// Import the functions you need from the SDKs you need import { initializeApp } from "https://www.gstatic.com/firebasejs/12.1.0/firebase-app.js"; import { getAnalytics } from "https://www.gstatic.com/firebasejs/12.1.0/firebase-analytics.js"; import { getAuth, GoogleAuthProvider, signInWithPopup, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, onAuthStateChanged, sendPasswordResetEmail, sendEmailVerification } from "https://www.gstatic.com/firebasejs/12.1.0/firebase-auth.js"; import { getFirestore, collection, doc, getDoc, getDocs, setDoc, addDoc, updateDoc, deleteDoc, onSnapshot, serverTimestamp, runTransaction, query, where, deleteField, increment, writeBatch } from "https://www.gstatic.com/firebasejs/12.1.0/firebase-firestore.js"; // Your web app's Firebase configuration const firebaseConfig = { apiKey: "AIzaSyDAjsx13A6kcZ13ILO0ukQj8r9CpBw5g7g", authDomain: "sgokher-ghori.firebaseapp.com", projectId: "sgokher-ghori", storageBucket: "sgokher-ghori.firebasestorage.app", messagingSenderId: "1081813421206", appId: "1:1081813421206:web:8431f7b770786066d4c6b7", measurementId: "G-5HVCR7VT5F" }; // Initialize Firebase const app = initializeApp(firebaseConfig); const analytics = getAnalytics(app); const auth = getAuth(app); const db = getFirestore(app); document.addEventListener('DOMContentLoaded', () => { const state = { currentUser: null, allProducts: [], allPromoCodes: [], siteSettings: {}, checkoutItems: [], isAdminInitialized: false, detailSwiper: null, appliedPromo: null, salesChartInstance: null, unsubscribe: { products: null, settings: null, orders: null, promoCodes: null, admins: null }, flashSaleTimerInterval: null, isInitialLoad: true, flashSaleSwiper: null, pageLoadPromise: null, verificationCheckInterval: null }; let selectedVariant = null; const dom = { loaderContainer: document.getElementById('loader-container'), productList: document.getElementById('product-list'), productListTitle: document.getElementById('product-list-title'), categoryFilters: document.getElementById('category-filters'), sortBySelect: document.getElementById('sort-by'), checkoutModal: document.getElementById('checkout-modal'), cancelCheckoutBtn: document.getElementById('cancel-checkout'), checkoutForm: document.getElementById('checkout-form'), orderSummaryContainer: document.getElementById('order-summary-items'), summarySubtotalSpan: document.getElementById('summary-subtotal'), summaryDeliveryChargeSpan: document.getElementById('summary-delivery-charge'), summaryTotalSpan: document.getElementById('summary-total'), deliveryLocationSelect: document.getElementById('delivery-location'), productDetailsModal: document.getElementById('product-details-modal'), productDetailsContent: document.getElementById('product-details-content'), authModal: document.getElementById('auth-modal'), adminPanel: document.getElementById('admin-panel'), searchInputs: [document.getElementById('search-bar'), document.getElementById('mobile-search-bar')], mobileMenuButton: document.getElementById('mobile-menu-button'), mobileMenu: document.getElementById('mobile-menu'), toast: document.getElementById('toast'), toastMessage: document.getElementById('toast-message'), discountPopup: document.getElementById('discount-popup'), closePopupBtn: document.getElementById('close-popup'), fabContainer: document.getElementById('fab-container'), fabMain: document.getElementById('fab-main'), promoSectionCheckout: document.getElementById('promo-section-checkout'), promoCodeInput: document.getElementById('promo-code-input'), applyPromoBtn: document.getElementById('apply-promo-btn'), promoMessage: document.getElementById('promo-message'), summaryDiscountRow: document.getElementById('summary-discount-row'), summaryDiscount: document.getElementById('summary-discount'), promoCodeForm: document.getElementById('promo-code-form'), promoCodeListContainer: document.getElementById('promo-code-list-container'), analytics: { totalRevenue: document.getElementById('total-revenue'), totalOrders: document.getElementById('total-orders'), totalCustomers: document.getElementById('total-customers'), salesChart: document.getElementById('sales-chart') }, themeToggles: [document.getElementById('theme-toggle'), document.getElementById('theme-toggle-mobile')], flashSale: { section: document.getElementById('flash-sale'), list: document.getElementById('flash-sale-list'), hours: document.getElementById('timer-hours'), minutes: document.getElementById('timer-minutes'), seconds: document.getElementById('timer-seconds'), }, popupForm: document.getElementById('popup-form'), addPopupBtn: document.getElementById('add-popup-btn'), infoPopup: { modal: document.getElementById('info-popup-modal'), closeBtn: document.getElementById('close-info-popup'), title: document.getElementById('info-popup-title'), message: document.getElementById('info-popup-message'), icon: document.getElementById('info-popup-icon') }, adminGuide: { modal: document.getElementById('admin-guide-modal'), showBtn: document.getElementById('show-admin-guide-btn'), closeBtn: document.getElementById('close-admin-guide-modal') }, adminListContainer: document.getElementById('admin-list-container') }; const showInfoPopup = (title, message, iconHtml) => { dom.infoPopup.title.textContent = title; dom.infoPopup.message.innerHTML = message; dom.infoPopup.icon.innerHTML = iconHtml; dom.infoPopup.modal.classList.remove('hidden'); }; const applyTheme = (theme) => { const icons = dom.themeToggles.map(btn => btn.querySelector('i')); if (theme === 'dark') { document.documentElement.classList.add('dark'); icons.forEach(icon => icon.className = 'fas fa-sun'); } else { document.documentElement.classList.remove('dark'); icons.forEach(icon => icon.className = 'fas fa-moon'); } if (state.salesChartInstance) renderAnalyticsDashboard(); }; const toggleTheme = () => { const newTheme = document.documentElement.classList.contains('dark') ? 'default' : 'dark'; applyTheme(newTheme); }; const showToast = (message, type = 'success') => { dom.toastMessage.textContent = message; dom.toast.className = `fixed top-20 right-5 z-[70] text-white px-6 py-3 rounded-lg shadow-lg ${type === 'success' ? 'bg-green-500' : 'bg-red-500'}`; dom.toast.classList.remove('hidden'); setTimeout(() => dom.toast.classList.add('hidden'), 4000); }; const calculatePrice = (price, discount) => Math.round(price - (price * (discount / 100))); const formatCurrency = (amount) => `৳${Math.round(amount).toLocaleString('en-IN')}`; const renderStarRating = (rating, totalReviews = 0) => { const fullStars = Math.floor(rating); const halfStar = rating % 1 >= 0.5 ? 1 : 0; const emptyStars = 5 - fullStars - halfStar; let starsHTML = Array(fullStars).fill('').join('') + (halfStar ? '' : '') + Array(emptyStars).fill('').join(''); return `
${starsHTML} ${totalReviews > 0 ? `(${totalReviews})` : ''}
`; }; const createProductCardHTML = (product) => { const finalPrice = calculatePrice(product.price, product.discount); const images = product.images && product.images.length > 0 ? product.images : ['https://placehold.co/400x400/F0F2F5/334155?text=No+Image']; const pinOrderBadge = product.pinOrder ? `
${product.pinOrder}
` : ''; return `
${state.currentUser?.role === 'admin' ? pinOrderBadge : ''}
${images.map(img => `
${product.name}
`).join('')}
${product.discount > 0 ? `
${product.discount}% OFF
` : ''} ${product.freeDelivery ? `
` : ''}

${product.name}

${formatCurrency(finalPrice)} ${product.discount > 0 ? `${formatCurrency(product.price)}` : ''}
`; }; const renderProducts = (productsToRender, container) => { container.innerHTML = productsToRender.length === 0 ? `

No products found.

` : productsToRender.map(createProductCardHTML).join(''); document.querySelectorAll('.product-card-swiper').forEach(el => new Swiper(el, { pagination: { el: '.swiper-pagination', clickable: true }, autoplay: { delay: 4000, disableOnInteraction: false }, loop: true })); if(container === dom.productList) addAdminButtonsToProducts(); }; const renderFlashSale = (products) => { if (state.flashSaleSwiper) { state.flashSaleSwiper.destroy(true, true); } dom.flashSale.list.innerHTML = products.map(p => `
${createProductCardHTML(p)}
`).join(''); dom.flashSale.list.querySelectorAll('.product-card-swiper').forEach(el => new Swiper(el, { pagination: { el: '.swiper-pagination', clickable: true }, autoplay: { delay: 4000, disableOnInteraction: false }, loop: true })); state.flashSaleSwiper = new Swiper('.flash-sale-swiper', { loop: products.length > 4, autoplay: { delay: 8000, disableOnInteraction: false, }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, breakpoints: { 320: { slidesPerView: 2, spaceBetween: 12 }, 768: { slidesPerView: 3, spaceBetween: 20 }, 1024: { slidesPerView: 4, spaceBetween: 24 }, } }); }; const renderSkeletonLoaders = (count) => { dom.productList.innerHTML = Array(count).fill('
').join(''); }; const renderCategories = () => { const categories = ['All', ...new Set(state.allProducts.map(p => p.category))]; dom.categoryFilters.innerHTML = categories.map(cat => ``).join(''); }; const renderProductDetails = async (product) => { const allImages = [...(product.images || []), ...(product.inventory || []).map(v => v.imageUrl)].filter(Boolean); const uniqueImages = [...new Set(allImages)]; let offerTagsHTML = ''; if (product.discount > 0) { offerTagsHTML += ``; } if (product.freeDelivery) { offerTagsHTML += ``; } dom.productDetailsContent.innerHTML = `

${product.name}

${renderStarRating(product.avgRating || 0, product.totalReviews || 0)}
${formatCurrency(calculatePrice(product.price, product.discount))} ${product.discount > 0 ? `${formatCurrency(product.price)}` : ''}
${offerTagsHTML ? `
${offerTagsHTML}
` : ''}

Variants

${(product.inventory || []).map((v, i) => ``).join('')}

Quantity:

1

${product.description}

Customer Reviews

Loading reviews...

`; dom.productDetailsContent.dataset.productId = product.id; dom.productDetailsModal.classList.remove('hidden'); const newUrl = `${window.location.pathname}?product=${product.id}`; history.pushState({ productId: product.id }, product.name, newUrl); if (state.detailSwiper) state.detailSwiper.destroy(); state.detailSwiper = new Swiper('.details-swiper', { navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' }, loop: false }); const hasInventory = product.inventory && product.inventory.length > 0; document.getElementById('variant-selection-area').classList.toggle('hidden', !hasInventory); if (!hasInventory) { selectedVariant = { imageUrl: product.images[0], size: 'N/A', stock: Infinity, price: calculatePrice(product.price, product.discount) }; } else { selectedVariant = null; setTimeout(() => { const firstVariantBtn = document.querySelector('#photo-variants .variant-btn'); if (firstVariantBtn) { firstVariantBtn.click(); const firstSizeBtn = document.querySelector('#size-variants .size-btn'); if (firstSizeBtn) firstSizeBtn.click(); } }, 0); } const deliveryTag = document.getElementById('details-delivery-tag'); if (deliveryTag) { deliveryTag.addEventListener('click', () => { showInfoPopup('Free Home Delivery', 'এই প্রোডাক্টটি সারাদেশে বিনামূল্যে হোম ডেলিভারি করা হবে।', ''); }); } const discountTag = document.getElementById('details-discount-tag'); if (discountTag) { discountTag.addEventListener('click', () => { const message = `এই প্রোডাক্টটির রেগুলার মূল্য ${formatCurrency(product.price)} এর উপর বিশেষ ${product.discount}% ছাড় চলছে!`; showInfoPopup('বিশেষ ডিসকাউন্ট!', message, ''); }); } updateOrderNowButton(selectedVariant); loadReviews(product.id); checkReviewEligibility(product.id); }; const renderAuthUI = (user) => { const authContainer = document.getElementById('auth-container'); const mobileAuthContainer = document.getElementById('mobile-auth-container'); const promoLoggedInView = document.getElementById('promo-logged-in-view'); const promoGuestView = document.getElementById('promo-guest-view'); if (user) { const userName = user.name || user.email.split('@')[0]; authContainer.innerHTML = `
`; mobileAuthContainer.innerHTML = ``; promoLoggedInView.classList.remove('hidden'); promoGuestView.classList.add('hidden'); if (user.role === 'admin') { dom.adminPanel.classList.remove('hidden'); if (!state.isAdminInitialized) { initAdminListeners(); renderAllAdminComponents(); state.isAdminInitialized = true; } } else { dom.adminPanel.classList.add('hidden'); } } else { authContainer.innerHTML = ``; mobileAuthContainer.innerHTML = ``; promoLoggedInView.classList.add('hidden'); promoGuestView.classList.remove('hidden'); dom.adminPanel.classList.add('hidden'); state.isAdminInitialized = false; } addAdminButtonsToProducts(); const openProductId = dom.productDetailsContent.dataset.productId; if(openProductId) checkReviewEligibility(openProductId); }; const addPopupAdminRow = (popup = { enabled: true, title: '', message: '' }) => { const container = document.getElementById('popup-list-container'); const div = document.createElement('div'); div.className = 'popup-entry p-2 border rounded space-y-2 relative'; div.innerHTML = `
`; div.querySelector('.remove-popup-btn').onclick = () => div.remove(); container.appendChild(div); }; const setActiveLoader = (loaderId = 'prime-spinner') => { document.querySelectorAll('.loader').forEach(l => l.classList.remove('active')); const loaderEl = document.getElementById(`loader-${loaderId}`); if (loaderEl) loaderEl.classList.add('active'); }; const renderSiteSettings = () => { document.getElementById('telegram-link').value = state.siteSettings.telegramLink || ''; document.getElementById('whatsapp-link').value = state.siteSettings.whatsappLink || ''; document.getElementById('facebook-link').value = state.siteSettings.facebookLink || ''; if(state.siteSettings.delivery){ document.getElementById('delivery-inside').value = state.siteSettings.delivery.inside || ''; document.getElementById('delivery-outside').value = state.siteSettings.delivery.outside || ''; } const loaderSelect = document.getElementById('loading-animation-select'); if (loaderSelect) { loaderSelect.value = state.siteSettings.loader || 'prime-spinner'; } const popupContainer = document.getElementById('popup-list-container'); popupContainer.innerHTML = ''; const popups = state.siteSettings.popups || [{ enabled: true, title: '', message: '' }]; popups.forEach(addPopupAdminRow); }; const updatePublicLinks = () => { const links = { 'telegram-support-link': state.siteSettings.telegramLink, 'whatsapp-support-link': state.siteSettings.whatsappLink, 'fab-telegram': state.siteSettings.telegramLink, 'fab-whatsapp': state.siteSettings.whatsappLink, 'footer-telegram-link': state.siteSettings.telegramLink, 'footer-whatsapp-link': state.siteSettings.whatsappLink, 'footer-facebook-link': state.siteSettings.facebookLink }; for (const id in links) { const el = document.getElementById(id); if(el) { el.href = links[id] || '#'; if(id.includes('support')) el.classList.toggle('hidden', !links[id]); } } }; const renderPopup = () => { const popups = state.siteSettings.popups || []; const enabledPopups = popups.filter(p => p.enabled && p.title && p.message); if (enabledPopups.length > 0) { const randomPopup = enabledPopups[Math.floor(Math.random() * enabledPopups.length)]; document.getElementById('popup-title-display').textContent = randomPopup.title; document.getElementById('popup-message-display').textContent = randomPopup.message; setTimeout(() => { dom.discountPopup.classList.remove('hidden', 'popup-exit'); dom.discountPopup.classList.add('popup-enter'); setTimeout(() => { dom.discountPopup.classList.remove('popup-enter'); dom.discountPopup.classList.add('popup-exit'); setTimeout(() => dom.discountPopup.classList.add('hidden'), 500); }, 3000); }, 2000); } }; const handleFilterAndSearch = () => { const activeCategory = document.querySelector('.category-btn.active')?.dataset.category || 'All'; const searchTerm = (dom.searchInputs[0].value || dom.searchInputs[1].value).toLowerCase().trim(); const sortBy = dom.sortBySelect.value; let productsToDisplay = []; if (searchTerm) { dom.flashSale.section.classList.add('hidden'); dom.productListTitle.textContent = `Search Results for "${searchTerm}"`; const calculateSearchScore = (product, term) => { let score = 0; const productName = product.name.toLowerCase(); const productDesc = product.description.toLowerCase(); const productKeywords = (product.keywords || []).map(k => k.toLowerCase()); if (productKeywords.some(k => k.includes(term))) score += 20; if (productName.includes(term)) score += 10; if (product.category.toLowerCase().includes(term)) score += 5; if (productDesc.includes(term)) score += 2; return score; }; productsToDisplay = state.allProducts .map(product => ({ ...product, searchScore: calculateSearchScore(product, searchTerm) })) .filter(product => product.searchScore > 0) .sort((a, b) => b.searchScore - a.searchScore); } else { dom.productListTitle.textContent = "All Products"; updateSpecialSections(); productsToDisplay = state.allProducts.filter(p => (activeCategory === 'All' || p.category === activeCategory) ); productsToDisplay.sort((a, b) => { const pinA = a.pinOrder || Infinity; const pinB = b.pinOrder || Infinity; if (pinA !== pinB) return pinA - pinB; if (sortBy === 'price-asc') return calculatePrice(a.price, a.discount) - calculatePrice(b.price, b.discount); if (sortBy === 'price-desc') return calculatePrice(b.price, b.discount) - calculatePrice(a.price, a.discount); return 0; }); } renderProducts(productsToDisplay, dom.productList); }; const updateFlashSaleTimer = () => { const now = new Date(); const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); const diff = tomorrow.getTime() - now.getTime(); if (diff < 0) return; const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); dom.flashSale.hours.textContent = String(hours).padStart(2, '0'); dom.flashSale.minutes.textContent = String(minutes).padStart(2, '0'); dom.flashSale.seconds.textContent = String(seconds).padStart(2, '0'); }; const startFlashSaleTimer = () => { if(state.flashSaleTimerInterval) clearInterval(state.flashSaleTimerInterval); updateFlashSaleTimer(); state.flashSaleTimerInterval = setInterval(updateFlashSaleTimer, 1000); }; const updateSpecialSections = () => { const flashSaleProducts = state.allProducts.filter(p => p.inFlashSale); const activeCategory = document.querySelector('.category-btn.active')?.dataset.category || 'All'; const searchTerm = (dom.searchInputs[0].value || dom.searchInputs[1].value).toLowerCase().trim(); if (flashSaleProducts.length > 0 && activeCategory === 'All' && !searchTerm) { dom.flashSale.section.classList.remove('hidden'); renderFlashSale(flashSaleProducts); } else { dom.flashSale.section.classList.add('hidden'); } }; const updateCheckoutSummary = () => { let subtotal = state.checkoutItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); const location = dom.deliveryLocationSelect.value; const hasFreeDeliveryItem = state.checkoutItems.some(item => { const product = state.allProducts.find(p => p.id === item.productId); return product && product.freeDelivery; }); let deliveryCharge = hasFreeDeliveryItem ? 0 : (location === 'inside' ? (state.siteSettings.delivery?.inside || 0) : location === 'outside' ? (state.siteSettings.delivery?.outside || 0) : 0); let total = subtotal + deliveryCharge; if (state.appliedPromo) { const discountAmount = (subtotal * state.appliedPromo.discountPercentage) / 100; total -= discountAmount; dom.summaryDiscountRow.classList.remove('hidden'); dom.summaryDiscount.textContent = `- ${formatCurrency(discountAmount)}`; } else { dom.summaryDiscountRow.classList.add('hidden'); } dom.orderSummaryContainer.innerHTML = state.checkoutItems.map(item => `
${item.name} (${item.size})
`).join(''); dom.summarySubtotalSpan.textContent = formatCurrency(subtotal); dom.summaryDeliveryChargeSpan.textContent = hasFreeDeliveryItem ? 'Free' : formatCurrency(deliveryCharge); dom.summaryTotalSpan.textContent = formatCurrency(total); } const populateAndShowCheckout = () => { if(state.checkoutItems.length === 0) return; state.appliedPromo = null; dom.promoCodeInput.value = ''; dom.promoMessage.textContent = ''; dom.promoCodeInput.disabled = false; dom.applyPromoBtn.disabled = false; dom.deliveryLocationSelect.value = ''; updateCheckoutSummary(); dom.checkoutModal.classList.remove('hidden'); }; const updateOrderNowButton = (variant) => { const orderNowBtn = document.getElementById('order-now-btn'); if (!orderNowBtn) return; const isReady = !!variant && !!variant.imageUrl && (!!variant.size || variant.size === 'N/A'); const isOutOfStock = isReady && variant.stock <= 0; orderNowBtn.disabled = !isReady || isOutOfStock; orderNowBtn.textContent = !isReady ? 'Select Variant & Size' : isOutOfStock ? 'Out of Stock' : 'Order Now'; } const closeDetailsModal = () => { dom.productDetailsModal.classList.add('hidden'); dom.productDetailsContent.innerHTML = ''; delete dom.productDetailsContent.dataset.productId; history.pushState(null, '', window.location.pathname); }; const handleAuth = async (e) => { e.preventDefault(); const authErrorEl = document.getElementById('auth-error'); authErrorEl.textContent = ''; const email = document.getElementById('email-input').value; const password = document.getElementById('password-input').value; const mode = e.target.closest('form').dataset.authMode; if (!email.endsWith('@gmail.com')) { authErrorEl.textContent = 'Only @gmail.com addresses are allowed.'; return; } try { if (mode === 'login') { const userCredential = await signInWithEmailAndPassword(auth, email, password); if (userCredential.user.emailVerified) { showToast('Login successful!'); dom.authModal.classList.add('hidden'); } else { authErrorEl.innerHTML = `Please verify your email. Check your inbox.
`; document.getElementById('resend-verification-btn').addEventListener('click', async () => { try { await sendEmailVerification(userCredential.user); showToast('Verification email sent!'); } catch (err) { showToast('Failed to send email. Please try again later.', 'error'); } }); await signOut(auth); // Sign out the unverified user to prevent confusion } } else { // Register mode const name = document.getElementById('register-name').value; const gender = document.getElementById('register-gender').value; if (!name || !gender) { authErrorEl.textContent = 'Please fill in all fields.'; return; } const userCredential = await createUserWithEmailAndPassword(auth, email, password); await setDoc(doc(db, 'users', userCredential.user.uid), { email, name, gender, role: 'customer' }); await sendEmailVerification(userCredential.user); dom.authModal.classList.add('hidden'); showInfoPopup( 'Registration Successful', 'A verification link has been sent to your email address. Please check your inbox and verify your email before logging in.', '' ); } } catch (error) { authErrorEl.textContent = error.message.replace('Firebase: ', ''); } }; const handleGoogleLogin = async () => { const provider = new GoogleAuthProvider(); try { const result = await signInWithPopup(auth, provider); const user = result.user; const userDocRef = doc(db, 'users', user.uid); const userDoc = await getDoc(userDocRef); if (!userDoc.exists()) { await setDoc(userDocRef, { email: user.email, name: user.displayName, gender: 'N/A', role: 'customer' }); } dom.authModal.classList.add('hidden'); showToast('Logged in successfully with Google!'); } catch (error) { document.getElementById('auth-error').textContent = error.message; } }; const handlePasswordReset = async () => { const email = document.getElementById('email-input').value; const authErrorEl = document.getElementById('auth-error'); if (!email) { authErrorEl.textContent = 'Please enter your email address.'; return; } try { await sendPasswordResetEmail(auth, email); showToast('Password reset link sent to your email!'); dom.authModal.classList.add('hidden'); } catch (error) { authErrorEl.textContent = error.message; } }; function setupEventListeners() { dom.searchInputs.forEach(input => input.addEventListener('keyup', handleFilterAndSearch)); dom.categoryFilters.addEventListener('click', e => { if (e.target.classList.contains('category-btn')) { document.querySelectorAll('.category-btn').forEach(btn => btn.classList.remove('active')); e.target.classList.add('active'); handleFilterAndSearch(); } }); dom.sortBySelect.addEventListener('change', handleFilterAndSearch); dom.deliveryLocationSelect.addEventListener('change', updateCheckoutSummary); dom.cancelCheckoutBtn.addEventListener('click', () => dom.checkoutModal.classList.add('hidden')); dom.checkoutForm.addEventListener('submit', async (e) => { e.preventDefault(); let subtotal = state.checkoutItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); const location = dom.deliveryLocationSelect.value; const hasFreeDeliveryItem = state.checkoutItems.some(item => state.allProducts.find(p => p.id === item.productId)?.freeDelivery); let deliveryCharge = hasFreeDeliveryItem ? 0 : (location === 'inside' ? (state.siteSettings.delivery?.inside || 0) : location === 'outside' ? (state.siteSettings.delivery?.outside || 0) : 0); let discountAmount = 0; if(state.appliedPromo) { discountAmount = (subtotal * state.appliedPromo.discountPercentage) / 100; } const total = subtotal - discountAmount + deliveryCharge; const newOrder = { userId: state.currentUser ? state.currentUser.uid : 'guest', customerInfo: { name: document.getElementById('customer-name').value, address: document.getElementById('customer-address').value, phone: document.getElementById('customer-phone').value, }, items: state.checkoutItems, subtotal, deliveryCharge, discountAmount, promoCode: state.appliedPromo ? state.appliedPromo.code : null, total, status: 'Pending', date: serverTimestamp() }; try { const newOrderRef = await addDoc(collection(db, 'orders'), newOrder); if (state.appliedPromo && state.currentUser) { const promoCodeRef = doc(db, "promoCodes", state.appliedPromo.code); await runTransaction(db, async (transaction) => { const promoDoc = await transaction.get(promoCodeRef); if (!promoDoc.exists()) throw "Promo code not found!"; transaction.update(promoCodeRef, { timesUsed: (promoDoc.data().timesUsed || 0) + 1 }); transaction.set(doc(db, "users", state.currentUser.uid, "usedPromoCodes", state.appliedPromo.code), { usedAt: serverTimestamp(), orderId: newOrderRef.id }); }); } dom.checkoutModal.classList.add('hidden'); dom.checkoutForm.reset(); showToast('Order placed successfully!'); } catch(error) { showToast("Failed to place order.", "error"); } }); const handleProductClick = (e) => { const productCard = e.target.closest('.product-card'); if (!productCard) return; const productId = productCard.dataset.productId; const product = state.allProducts.find(p => p.id === productId); if (!product) return; const isOrderNowBtn = e.target.classList.contains('order-now-btn'); const hasVariants = product.inventory && product.inventory.length > 0; if (isOrderNowBtn && !hasVariants) { state.checkoutItems = [{ productId: product.id, name: product.name, price: calculatePrice(product.price, product.discount), imageUrl: product.images[0], size: 'N/A', quantity: 1 }]; populateAndShowCheckout(); } else { renderProductDetails(product); } }; dom.productList.addEventListener('click', handleProductClick); dom.flashSale.list.addEventListener('click', handleProductClick); dom.productDetailsModal.addEventListener('click', e => { if (e.target === dom.productDetailsModal || e.target.closest('#close-product-details')) closeDetailsModal(); }); dom.productDetailsContent.addEventListener('click', (e) => { const product = state.allProducts.find(p => p.id === dom.productDetailsContent.dataset.productId); if (!product) return; const variantBtn = e.target.closest('.variant-btn'); const sizeBtn = e.target.closest('.size-btn'); const quantityBtn = e.target.closest('#quantity-increase, #quantity-decrease'); const orderNowBtn = e.target.closest('#order-now-btn'); const shareBtn = e.target.closest('#share-product-btn'); const quantityValueEl = document.getElementById('quantity-value'); if (shareBtn) navigator.clipboard.writeText(window.location.href).then(() => showToast('Link copied!')); if (variantBtn) { document.querySelectorAll('.variant-btn').forEach(btn => btn.classList.remove('selected')); variantBtn.classList.add('selected'); const variantData = product.inventory[variantBtn.dataset.index]; if (state.detailSwiper) { const imageIndex = [...new Set([...(product.images || []), ...(product.inventory || []).map(v => v.imageUrl)].filter(Boolean))].findIndex(img => img === variantData.imageUrl); if (imageIndex !== -1) state.detailSwiper.slideTo(imageIndex); } document.getElementById('size-variants').innerHTML = variantData.sizes.map(s => ``).join(''); document.getElementById('size-selection-container').classList.remove('hidden'); selectedVariant = null; document.getElementById('stock-info').textContent = ''; } if (sizeBtn) { document.querySelectorAll('.size-btn').forEach(btn => btn.classList.remove('selected')); sizeBtn.classList.add('selected'); const variantPrice = parseFloat(sizeBtn.dataset.price); const finalPrice = !isNaN(variantPrice) && variantPrice > 0 ? variantPrice : calculatePrice(product.price, product.discount); selectedVariant = { imageUrl: sizeBtn.dataset.image, size: sizeBtn.dataset.size, stock: parseInt(sizeBtn.dataset.stock, 10), price: finalPrice }; const priceContainer = document.getElementById('details-price-container'); priceContainer.querySelector('span:first-child').textContent = formatCurrency(finalPrice); const originalPriceSpan = priceContainer.querySelector('.line-through'); if (originalPriceSpan) { originalPriceSpan.style.display = (!isNaN(variantPrice) && variantPrice > 0) ? 'none' : 'inline'; } const stockInfoEl = document.getElementById('stock-info'); stockInfoEl.textContent = selectedVariant.stock > 0 ? `${selectedVariant.stock} items available` : 'Out of Stock'; stockInfoEl.style.color = selectedVariant.stock > 0 ? '' : 'red'; } if (quantityBtn && quantityValueEl) { let qty = parseInt(quantityValueEl.textContent); qty += quantityBtn.id === 'quantity-increase' ? 1 : -1; if (qty > 0 && qty <= (selectedVariant?.stock ?? Infinity)) quantityValueEl.textContent = qty; } if (orderNowBtn && !orderNowBtn.disabled) { state.checkoutItems = [{ productId: product.id, name: product.name, price: selectedVariant.price, imageUrl: selectedVariant.imageUrl || product.images[0], size: selectedVariant.size, quantity: parseInt(quantityValueEl.textContent) }]; closeDetailsModal(); populateAndShowCheckout(); } updateOrderNowButton(selectedVariant); }); const nameInput = document.getElementById('register-name'); const genderInput = document.getElementById('register-gender'); document.getElementById('google-login-btn').addEventListener('click', handleGoogleLogin); document.getElementById('show-email-login-btn').addEventListener('click', () => { document.getElementById('initial-auth-view').classList.add('hidden'); document.getElementById('email-form').classList.remove('hidden'); document.getElementById('auth-modal-title').textContent = 'Login'; document.getElementById('email-form-submit').textContent = 'Login'; document.getElementById('email-form').dataset.authMode = 'login'; nameInput.classList.add('hidden'); genderInput.classList.add('hidden'); }); document.getElementById('show-email-register-btn').addEventListener('click', () => { document.getElementById('initial-auth-view').classList.add('hidden'); document.getElementById('email-form').classList.remove('hidden'); document.getElementById('auth-modal-title').textContent = 'Register'; document.getElementById('email-form-submit').textContent = 'Register'; document.getElementById('email-form').dataset.authMode = 'register'; nameInput.classList.remove('hidden'); genderInput.classList.remove('hidden'); }); document.getElementById('back-to-auth-initial').addEventListener('click', () => { document.getElementById('email-form').classList.add('hidden'); document.getElementById('initial-auth-view').classList.remove('hidden'); document.getElementById('auth-modal-title').textContent = 'Login'; document.getElementById('auth-error').textContent = ''; }); document.getElementById('email-form').addEventListener('submit', handleAuth); document.getElementById('forgot-password-btn').addEventListener('click', handlePasswordReset); document.body.addEventListener('click', async e => { if (e.target.id === 'login-btn' || e.target.id === 'mobile-login-btn' || e.target.id === 'login-for-promo') { if (e.target.id === 'login-for-promo') dom.checkoutModal.classList.add('hidden'); dom.authModal.classList.remove('hidden'); } if (e.target.id === 'close-auth-modal') dom.authModal.classList.add('hidden'); if (e.target.closest('#user-menu-btn')) document.getElementById('user-dropdown').classList.toggle('hidden'); if (e.target.id === 'logout-btn' || e.target.id === 'mobile-logout-btn') await signOut(auth); }); dom.mobileMenuButton.addEventListener('click', () => dom.mobileMenu.classList.toggle('hidden')); dom.fabMain.addEventListener('click', () => dom.fabContainer.classList.toggle('open')); dom.closePopupBtn.addEventListener('click', () => dom.discountPopup.classList.add('hidden')); dom.applyPromoBtn.addEventListener('click', handleApplyPromo); dom.themeToggles.forEach(btn => btn.addEventListener('click', toggleTheme)); dom.infoPopup.closeBtn.addEventListener('click', () => dom.infoPopup.modal.classList.add('hidden')); // Admin guide modal listeners dom.adminGuide.showBtn.addEventListener('click', () => dom.adminGuide.modal.classList.remove('hidden')); dom.adminGuide.closeBtn.addEventListener('click', () => dom.adminGuide.modal.classList.add('hidden')); } async function checkReviewEligibility(productId) { const reviewFormContainer = document.getElementById('review-form-container'); if (!reviewFormContainer) return; reviewFormContainer.innerHTML = ''; if (!state.currentUser) { reviewFormContainer.innerHTML = `

Please to write a review.

`; document.getElementById('login-for-review').addEventListener('click', () => { closeDetailsModal(); dom.authModal.classList.remove('hidden'); }); return; } const q = query(collection(db, 'orders'), where("userId", "==", state.currentUser.uid), where("status", "==", "Delivered")); const querySnapshot = await getDocs(q); if (querySnapshot.docs.some(doc => doc.data().items?.some(item => item.productId === productId))) { renderReviewForm(productId); } else { reviewFormContainer.innerHTML = `

You can only review purchased products.

`; } } function renderReviewForm(productId) { const reviewFormContainer = document.getElementById('review-form-container'); reviewFormContainer.innerHTML = `

Write Your Review

`; const starsContainer = reviewFormContainer.querySelector('.review-form-stars'); starsContainer.addEventListener('mouseover', e => { if (e.target.matches('.fa-star')) starsContainer.querySelectorAll('.fa-star').forEach(s => s.style.color = s.dataset.value <= e.target.dataset.value ? 'var(--secondary-color)' : '#cbd5e1'); }); starsContainer.addEventListener('mouseout', () => starsContainer.querySelectorAll('.fa-star').forEach(s => s.style.color = s.dataset.value <= starsContainer.dataset.rating ? 'var(--secondary-color)' : '#cbd5e1')); starsContainer.addEventListener('click', e => { if (e.target.matches('.fa-star')) starsContainer.dataset.rating = e.target.dataset.value; }); document.getElementById('review-form').addEventListener('submit', (e) => handleReviewSubmit(e, productId)); } async function handleReviewSubmit(e, productId) { e.preventDefault(); const rating = parseInt(document.querySelector('.review-form-stars').dataset.rating, 10); if (rating === 0) { showToast("Please select a star rating.", "error"); return; } const reviewData = { userId: state.currentUser.uid, userName: state.currentUser.name, rating, text: document.getElementById('review-text').value, createdAt: serverTimestamp() }; await addDoc(collection(db, 'products', productId, 'reviews'), reviewData); await runTransaction(db, async (transaction) => { const productRef = doc(db, 'products', productId); const productDoc = await transaction.get(productRef); if (!productDoc.exists()) return; const { totalReviews = 0, avgRating = 0 } = productDoc.data(); const newTotal = totalReviews + 1; const newAvg = ((avgRating * totalReviews) + rating) / newTotal; transaction.update(productRef, { totalReviews: newTotal, avgRating: parseFloat(newAvg.toFixed(2)) }); }); showToast("Thank you for your review!"); loadReviews(productId); } async function loadReviews(productId) { const reviewsList = document.getElementById('reviews-list'); if (!reviewsList) return; const q = query(collection(db, 'products', productId, 'reviews')); const querySnapshot = await getDocs(q); reviewsList.innerHTML = querySnapshot.empty ? `

No reviews yet.

` : querySnapshot.docs.map(d => { const r = d.data(); return `

${r.userName}

${renderStarRating(r.rating)}

${r.text}

`; }).join(''); } async function handleApplyPromo() { const code = dom.promoCodeInput.value.trim().toUpperCase(); dom.promoMessage.textContent = ''; state.appliedPromo = null; if (!code || !state.currentUser) { updateCheckoutSummary(); return; } const promoDoc = await getDoc(doc(db, "promoCodes", code)); if (!promoDoc.exists() || !promoDoc.data().isActive) { dom.promoMessage.textContent = "Invalid code."; return; } const promo = promoDoc.data(); if (promo.timesUsed >= promo.usageLimit) { dom.promoMessage.textContent = "Usage limit reached."; return; } const userPromoDoc = await getDoc(doc(db, "users", state.currentUser.uid, "usedPromoCodes", code)); if (userPromoDoc.exists()) { dom.promoMessage.textContent = "Already used."; return; } const subtotal = state.checkoutItems.reduce((sum, item) => sum + (item.price * item.quantity), 0); if (promo.minPrice && subtotal < promo.minPrice) { dom.promoMessage.textContent = `Min order: ${formatCurrency(promo.minPrice)}`; return; } if (promo.maxPrice && subtotal > promo.maxPrice) { dom.promoMessage.textContent = `Max order: ${formatCurrency(promo.maxPrice)}`; return; } if (promo.excludedProductIds?.some(id => state.checkoutItems.some(item => item.productId === id))) { dom.promoMessage.textContent = "Not valid for some items."; return; } state.appliedPromo = { code, discountPercentage: promo.discountPercentage }; dom.promoMessage.textContent = `Success! ${promo.discountPercentage}% off.`; updateCheckoutSummary(); } function generateKeywordsAI(name, description) { const stopWords = new Set(['a', 'an', 'the', 'in', 'on', 'for', 'and', 'with', 'to', 'of', 'is', 'it', 'all', 'new']); const text = `${name} ${description}`.toLowerCase(); const words = text.replace(/[^\w\s]/g, '').split(/\s+/); return [...new Set(words.filter(word => word && !stopWords.has(word)))]; } function initAdminListeners() { document.getElementById('add-product-form').addEventListener('submit', handleProductFormSubmit); document.getElementById('add-image-btn').addEventListener('click', () => addAdditionalImageInput()); document.getElementById('add-variant-btn').addEventListener('click', () => addPhotoVariantBlock()); document.getElementById('add-new-category-btn').addEventListener('click', handleAddNewCategory); document.getElementById('rename-category-btn').addEventListener('click', handleRenameCategory); document.getElementById('ai-suggest-keywords-btn').addEventListener('click', handleAIKeywordSuggest); document.getElementById('clear-product-form').addEventListener('click', clearProductForm); document.getElementById('order-tabs').addEventListener('click', handleOrderTabClick); document.getElementById('order-list-container').addEventListener('click', handleOrderStatusChange); document.getElementById('settings-form').addEventListener('submit', handleSettingsFormSubmit); dom.popupForm.addEventListener('submit', handlePopupFormSubmit); dom.addPopupBtn.addEventListener('click', () => addPopupAdminRow()); dom.promoCodeForm.addEventListener('submit', handlePromoCodeFormSubmit); dom.productList.addEventListener('click', handleProductAdminActions); dom.promoCodeListContainer.addEventListener('click', e => { if (e.target.classList.contains('edit-promo-btn')) populatePromoFormForEdit(e.target.dataset.code); }); document.getElementById('add-admin-form').addEventListener('submit', handleMakeAdmin); dom.adminListContainer.addEventListener('click', handleRemoveAdmin); } async function handleMakeAdmin(e) { e.preventDefault(); const email = document.getElementById('new-admin-email').value.trim(); if (!email) { showToast("Please enter an email.", "error"); return; } try { const q = query(collection(db, "users"), where("email", "==", email)); const querySnapshot = await getDocs(q); if (querySnapshot.empty) { showToast("User with this email not found.", "error"); return; } const userDoc = querySnapshot.docs[0]; await updateDoc(doc(db, 'users', userDoc.id), { role: 'admin' }); showToast(`Success! ${email} is now an admin.`, 'success'); document.getElementById('new-admin-email').value = ''; } catch (error) { showToast("An error occurred. Please try again.", "error"); } } async function handleRemoveAdmin(e) { const removeBtn = e.target.closest('.remove-admin-btn'); if (!removeBtn) return; const uid = removeBtn.dataset.uid; const email = removeBtn.dataset.email; if (confirm(`Are you sure you want to remove admin rights for ${email}?`)) { try { await updateDoc(doc(db, 'users', uid), { role: 'customer' }); showToast(`${email} is no longer an admin.`, 'success'); } catch (error) { showToast('Failed to remove admin.', 'error'); } } } function populatePromoFormForEdit(code) { const promo = state.allPromoCodes.find(p => p.id === code); if (!promo) return; const form = dom.promoCodeForm; form.querySelector('#promo-code').value = promo.code; form.querySelector('#promo-code').readOnly = true; form.querySelector('#promo-discount').value = promo.discountPercentage; form.querySelector('#promo-limit').value = promo.usageLimit; form.querySelector('#promo-min-price').value = promo.minPrice || ''; form.querySelector('#promo-max-price').value = (promo.maxPrice && promo.maxPrice !== Infinity) ? promo.maxPrice : ''; form.querySelector('#promo-active').checked = promo.isActive; form.querySelectorAll('#promo-exclude-products-container input').forEach(cb => cb.checked = promo.excludedProductIds?.includes(cb.value)); } async function handlePromoCodeFormSubmit(e) { e.preventDefault(); const form = e.target; const code = form.querySelector('#promo-code').value.trim().toUpperCase(); if (!code) { showToast("Promo code is required.", "error"); return; } const promoData = { code, discountPercentage: Number(form.querySelector('#promo-discount').value), usageLimit: Number(form.querySelector('#promo-limit').value), minPrice: Number(form.querySelector('#promo-min-price').value) || 0, maxPrice: Number(form.querySelector('#promo-max-price').value) || Infinity, excludedProductIds: Array.from(form.querySelectorAll('#promo-exclude-products-container input:checked')).map(cb => cb.value), isActive: form.querySelector('#promo-active').checked }; const promoDocRef = doc(db, "promoCodes", code); const isUpdating = (await getDoc(promoDocRef)).exists(); await setDoc(promoDocRef, isUpdating ? promoData : { ...promoData, timesUsed: 0, createdAt: serverTimestamp() }, { merge: true }); showToast(`Promo code ${isUpdating ? 'updated' : 'saved'}!`); form.reset(); form.querySelector('#promo-code').readOnly = false; } function renderPromoCodes(promoCodes) { dom.promoCodeListContainer.innerHTML = `

Existing Codes

${promoCodes.map(promo => `

${promo.code} ${promo.discountPercentage}% OFF

Limit: ${promo.timesUsed || 0}/${promo.usageLimit} | Status: ${promo.isActive ? 'Active' : 'Inactive'}

Conditions: Min ${formatCurrency(promo.minPrice || 0)}, Max ${promo.maxPrice === Infinity ? 'N/A' : formatCurrency(promo.maxPrice)}

`).join('') || '

No codes yet.

'}
`; } async function handleProductAdminActions(e) { const editBtn = e.target.closest('.admin-edit-btn'); const deleteBtn = e.target.closest('.admin-delete-btn'); const pinBtn = e.target.closest('.admin-pin-btn'); if (editBtn) populateProductFormForEdit(editBtn.dataset.productId); if (deleteBtn && confirm('Are you sure you want to delete this product?')) { await deleteDoc(doc(db, 'products', deleteBtn.dataset.productId)); showToast('Product deleted.', 'error'); } if (pinBtn) { const productId = pinBtn.dataset.productId; const currentProduct = state.allProducts.find(p => p.id === productId); const currentPin = currentProduct?.pinOrder || ''; const order = prompt("Enter display order number (e.g., 1, 2). Enter 0 or leave blank to unpin.", currentPin); if (order === null) return; const orderNumber = parseInt(order, 10); if (!isNaN(orderNumber) && orderNumber > 0) { await updateDoc(doc(db, 'products', productId), { pinOrder: orderNumber }); showToast(`Product pinned to position ${orderNumber}.`); } else { await updateDoc(doc(db, 'products', productId), { pinOrder: deleteField() }); showToast('Product unpinned.'); } } } function populateProductFormForEdit(productId) { const product = state.allProducts.find(p => p.id === productId); if (!product) return; clearProductForm(); document.getElementById('product-id').value = product.id; document.getElementById('product-name').value = product.name; document.getElementById('product-desc').value = product.description; document.getElementById('product-keywords').value = (product.keywords || []).join(', '); document.getElementById('product-category-select').value = product.category; document.getElementById('product-price').value = product.price; document.getElementById('product-image-url').value = product.images[0]; document.getElementById('product-discount').value = product.discount || ''; document.getElementById('product-free-delivery').checked = product.freeDelivery || false; document.getElementById('product-flash-sale').checked = product.inFlashSale || false; (product.images.slice(1) || []).forEach(url => addAdditionalImageInput(url)); (product.inventory || []).forEach(variant => addPhotoVariantBlock(variant)); window.scrollTo({ top: dom.adminPanel.offsetTop, behavior: 'smooth' }); } function addAdditionalImageInput(url = '') { const container = document.getElementById('additional-images-container'); const div = document.createElement('div'); div.className = 'flex items-center gap-2'; div.innerHTML = ``; div.querySelector('.remove-btn').onclick = () => div.remove(); container.appendChild(div); } function addPhotoVariantBlock(variant = null) { const container = document.getElementById('inventory-container'); const div = document.createElement('div'); div.className = 'p-3 border rounded-md bg-gray-50 space-y-2'; div.innerHTML = `
`; container.appendChild(div); const sizesContainer = div.querySelector('.sizes-container'); if (variant?.sizes) variant.sizes.forEach(size => addInventorySizeInput(sizesContainer, size)); div.querySelector('.add-size-btn').onclick = () => addInventorySizeInput(sizesContainer); div.querySelector('.font-bold').onclick = () => div.remove(); } function addInventorySizeInput(container, size = null) { const div = document.createElement('div'); div.className = 'flex items-center gap-2'; div.innerHTML = ` `; div.querySelector('.remove-btn').onclick = () => div.remove(); container.appendChild(div); } async function handleProductFormSubmit(e) { e.preventDefault(); const productId = document.getElementById('product-id').value; const images = [document.getElementById('product-image-url').value, ...Array.from(document.querySelectorAll('#additional-images-container input')).map(i => i.value)].filter(Boolean); const inventory = Array.from(document.querySelectorAll('#inventory-container > div')).map(v => { const imageUrl = v.querySelector('input[type="url"]').value; const sizes = Array.from(v.querySelectorAll('.sizes-container > div')).map(s => ({ size: s.querySelector('input[type="text"]').value, stock: parseInt(s.querySelectorAll('input[type="number"]')[0].value, 10) || 0, price: parseFloat(s.querySelectorAll('input[type="number"]')[1].value) || null })).filter(s => s.size || s.stock > 0); return { imageUrl, sizes }; }).filter(v => v.imageUrl || v.sizes.length > 0); const keywords = document.getElementById('product-keywords').value.split(',').map(k => k.trim()).filter(Boolean); const productData = { name: document.getElementById('product-name').value, description: document.getElementById('product-desc').value, category: document.getElementById('product-category-select').value, price: parseFloat(document.getElementById('product-price').value), discount: parseFloat(document.getElementById('product-discount').value) || 0, images, inventory, keywords, freeDelivery: document.getElementById('product-free-delivery').checked, inFlashSale: document.getElementById('product-flash-sale').checked }; const ref = productId ? doc(db, 'products', productId) : doc(collection(db, 'products')); if (!productId) { productData.totalReviews = 0; productData.avgRating = 0; } await setDoc(ref, productData, { merge: true }); showToast(`Product ${productId ? 'updated' : 'added'}!`); clearProductForm(); } function handleAddNewCategory() { const cat = prompt("New category name:"); if (cat) { const select = document.getElementById('product-category-select'); select.add(new Option(cat, cat)); select.value = cat; } } async function handleRenameCategory() { const select = document.getElementById('product-category-select'); const oldCategory = select.value; if (!oldCategory) { showToast("Please select a category to rename.", "error"); return; } const newCategory = prompt(`Enter the new name for "${oldCategory}":`, oldCategory); if (!newCategory || newCategory.trim() === '' || newCategory === oldCategory) { showToast("Rename cancelled or invalid name.", "error"); return; } try { const q = query(collection(db, "products"), where("category", "==", oldCategory)); const snapshot = await getDocs(q); const batch = writeBatch(db); snapshot.docs.forEach(doc => { batch.update(doc.ref, { category: newCategory }); }); await batch.commit(); showToast(`Category '${oldCategory}' renamed to '${newCategory}' successfully!`, 'success'); } catch (error) { showToast("Failed to rename category. Please try again.", "error"); } } function handleAIKeywordSuggest() { const name = document.getElementById('product-name').value; const desc = document.getElementById('product-desc').value; if (!name && !desc) { showToast("Please enter a name or description first.", "error"); return; } const keywords = generateKeywordsAI(name, desc); document.getElementById('product-keywords').value = keywords.join(', '); } function clearProductForm() { document.getElementById('add-product-form').reset(); document.getElementById('additional-images-container').innerHTML = ''; document.getElementById('inventory-container').innerHTML = ''; } function renderOrders(orders) { const status = document.querySelector('.order-tab.active').dataset.status; const filtered = orders.filter(o => o.status === status).sort((a,b) => b.date.toMillis() - a.date.toMillis()); document.getElementById('order-list-container').innerHTML = filtered.length ? filtered.map(o => { const { name, address, phone } = o.customerInfo; const itemsHTML = o.items.map(i => `
  • ${i.name}

    ${i.quantity}x ${i.name}

    Size: ${i.size}

  • `).join(''); return `

    ${name} - ${phone}

    ${address}

    Order ID: ${o.id}

    Total: ${formatCurrency(o.total)}

    `; }).join('') : `

    No ${status} orders.

    `; } function renderAdminList(admins) { dom.adminListContainer.innerHTML = admins.map(admin => { const isSelf = state.currentUser && state.currentUser.uid === admin.id; const canRemove = admins.length > 1 && !isSelf; return `
    ${admin.email} ${isSelf ? '(You)' : ''} ${canRemove ? `` : ''}
    `; }).join('') || '

    No admins found.

    '; } function handleOrderTabClick(e) { if(e.target.classList.contains('order-tab')) { document.querySelectorAll('.order-tab').forEach(t => t.classList.remove('active')); e.target.classList.add('active'); listenToOrders(); } } async function handleOrderStatusChange(e) { if (e.target.classList.contains('order-status-select')) await updateDoc(doc(db, 'orders', e.target.dataset.orderId), { status: e.target.value }); } async function handleSettingsFormSubmit(e) { e.preventDefault(); const data = { telegramLink: document.getElementById('telegram-link').value, whatsappLink: document.getElementById('whatsapp-link').value, facebookLink: document.getElementById('facebook-link').value, delivery: { inside: parseFloat(document.getElementById('delivery-inside').value), outside: parseFloat(document.getElementById('delivery-outside').value) }, loader: document.getElementById('loading-animation-select').value }; await setDoc(doc(db, 'settings', 'site'), data, { merge: true }); showToast('Settings saved!'); } async function handlePopupFormSubmit(e) { e.preventDefault(); const popupEntries = document.querySelectorAll('.popup-entry'); const popups = Array.from(popupEntries).map(entry => ({ enabled: entry.querySelector('.popup-enabled').checked, title: entry.querySelector('.popup-title').value.trim(), message: entry.querySelector('.popup-message').value.trim(), })); const data = { popups }; await setDoc(doc(db, 'settings', 'site'), data, { merge: true }); showToast('Popups updated!'); } function populateCategorySelect() { const select = document.getElementById('product-category-select'); select.innerHTML = [...new Set(state.allProducts.map(p => p.category))].map(c => ``).join(''); } function addAdminButtonsToProducts() { document.querySelectorAll('.admin-actions').forEach(el => el.remove()); if (state.currentUser?.role !== 'admin') return; document.querySelectorAll('.product-card').forEach(card => { const productId = card.dataset.productId; card.querySelector('.product-image-container').insertAdjacentHTML('afterbegin', `
    `); }); } function renderPromoProductExclusionList() { document.getElementById('promo-exclude-products-container').innerHTML = state.allProducts.map(p => `
    `).join(''); } async function renderAnalyticsDashboard() { const [ordersSnap, usersSnap] = await Promise.all([getDocs(collection(db, 'orders')), getDocs(collection(db, 'users'))]); const orders = ordersSnap.docs.map(d => d.data()); const totalRevenue = orders.filter(o => o.status === 'Delivered').reduce((sum, o) => sum + o.total, 0); const salesThisMonth = orders.filter(o => o.status === 'Delivered' && o.date.toDate().getMonth() === new Date().getMonth()).reduce((sum, o) => sum + o.total, 0); dom.analytics.totalRevenue.textContent = formatCurrency(totalRevenue); dom.analytics.totalOrders.textContent = orders.length; dom.analytics.totalCustomers.textContent = usersSnap.size; const labels = Array.from({length: 7}, (_, i) => { const d = new Date(); d.setDate(d.getDate() - i); return d.toLocaleDateString('en-US', {month: 'short', day: 'numeric'}); }).reverse(); const salesData = labels.map(label => orders.filter(o => o.status === 'Delivered' && o.date.toDate().toLocaleDateString('en-US', {month: 'short', day: 'numeric'}) === label).reduce((sum, o) => sum + o.total, 0)); if (state.salesChartInstance) state.salesChartInstance.destroy(); state.salesChartInstance = new Chart(dom.analytics.salesChart, { type: 'line', data: { labels, datasets: [{ label: 'Daily Sales', data: salesData, borderColor: 'var(--primary-color)', tension: 0.3 }] }, options: { responsive: true } }); } function renderAllAdminComponents() { populateCategorySelect(); renderPromoProductExclusionList(); renderSiteSettings(); listenToOrders(); listenToPromoCodes(); listenToAdmins(); renderAnalyticsDashboard(); } function listenTo(coll, callback) { if (state.unsubscribe[coll]) state.unsubscribe[coll](); state.unsubscribe[coll] = onSnapshot(collection(db, coll), snap => callback(snap.docs.map(d => ({ id: d.id, ...d.data() })))); } const handleDirectLink = () => { const params = new URLSearchParams(window.location.search); const productId = params.get('product'); if (productId) { const product = state.allProducts.find(p => p.id === productId); if (product) { setTimeout(() => renderProductDetails(product), 100); } } }; function listenToProducts() { state.pageLoadPromise = new Promise(resolve => { listenTo('products', products => { state.allProducts = products; handleFilterAndSearch(); renderCategories(); updateSpecialSections(); if (state.isAdminInitialized) { populateCategorySelect(); renderPromoProductExclusionList(); } if(state.isInitialLoad) { handleDirectLink(); resolve(); } }); }); } function listenToSettings() { state.unsubscribe.settings = onSnapshot(doc(db, 'settings', 'site'), doc => { if (doc.exists()) { state.siteSettings = doc.data(); updatePublicLinks(); if (state.isInitialLoad) { renderPopup(); } if(state.isAdminInitialized) renderSiteSettings(); } }); } function listenToOrders() { listenTo('orders', orders => { if (state.isAdminInitialized) renderOrders(orders); }); } function listenToPromoCodes() { listenTo('promoCodes', codes => { state.allPromoCodes = codes; if (state.isAdminInitialized) renderPromoCodes(codes); }); } function listenToAdmins() { if (state.unsubscribe.admins) state.unsubscribe.admins(); const q = query(collection(db, "users"), where("role", "==", "admin")); state.unsubscribe.admins = onSnapshot(q, snap => { const admins = snap.docs.map(d => ({ id: d.id, ...d.data() })); if (state.isAdminInitialized) { renderAdminList(admins); } }); } onAuthStateChanged(auth, async (user) => { if (user) { const userDocRef = doc(db, 'users', user.uid); const userDoc = await getDoc(userDocRef); const isAdminEmail = user.email === 'arko2449t@gmail.com'; if (userDoc.exists()) { const userData = userDoc.data(); if (isAdminEmail && userData.role !== 'admin') { await updateDoc(userDocRef, { role: 'admin' }); userData.role = 'admin'; } if (!userData.name || !userData.gender) { state.currentUser = { uid: user.uid, name: user.email.split('@')[0], gender: 'N/A', ...userData }; } else { state.currentUser = { uid: user.uid, ...userData }; } } else { const newUser = { email: user.email, role: isAdminEmail ? 'admin' : 'customer', name: user.displayName || user.email.split('@')[0], gender: 'N/A' }; await setDoc(userDocRef, newUser); state.currentUser = { uid: user.uid, ...newUser }; } } else { state.currentUser = null; } renderAuthUI(state.currentUser); handleFilterAndSearch(); }); async function main() { renderSkeletonLoaders(8); setupEventListeners(); listenToProducts(); listenToSettings(); startFlashSaleTimer(); try { const settingsDoc = await getDoc(db, 'settings', 'site'); if (settingsDoc.exists()) { state.siteSettings = settingsDoc.data(); setActiveLoader(state.siteSettings.loader || 'prime-spinner'); } } catch(e) { console.error("Could not fetch initial settings.", e); setActiveLoader('prime-spinner'); } const timeoutPromise = new Promise(resolve => setTimeout(resolve, 2000)); await Promise.race([state.pageLoadPromise, timeoutPromise]); dom.loaderContainer.classList.add('hidden'); state.isInitialLoad = false; } main(); });