// 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 => `
`).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 = `
${uniqueImages.map(img => `
`).join('')}
${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('')}
${product.description}
`;
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 = ``;
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.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();
});