/** * Customer Authentication State Manager * Handles checking login status and updating UI accordingly */ (function () { "use strict"; console.log("[customer-auth.js] Loading..."); // Customer authentication state window.CustomerAuth = { user: null, isLoggedIn: false, isLoading: true, // Initialize - check session on page load async init() { console.log("[CustomerAuth] Initializing..."); await this.checkSession(); this.updateNavbar(); this.setupEventListeners(); }, // Check if user is logged in async checkSession() { try { const response = await fetch("/api/customers/session", { credentials: "include", }); if (response.ok) { const data = await response.json(); if (data.success && data.loggedIn) { this.user = data.customer; this.isLoggedIn = true; console.log("[CustomerAuth] User logged in:", this.user.firstName); // Sync local cart/wishlist with server await this.syncCartWithServer(); await this.syncWishlistWithServer(); } else { this.user = null; this.isLoggedIn = false; } } else { this.user = null; this.isLoggedIn = false; } } catch (error) { console.error("[CustomerAuth] Session check failed:", error); this.user = null; this.isLoggedIn = false; } this.isLoading = false; }, // Update navbar based on auth state updateNavbar() { // Find the sign in link/button in nav-actions const navActions = document.querySelector(".nav-actions"); if (!navActions) { console.warn("[CustomerAuth] nav-actions not found"); return; } // Find the existing sign in link const signinLink = navActions.querySelector( 'a[href="/signin"], a[href="/account"]', ); if (this.isLoggedIn && this.user) { // User is logged in - update UI if (signinLink) { // Replace with user dropdown const userDropdown = document.createElement("div"); userDropdown.className = "user-dropdown"; userDropdown.innerHTML = `
My Account My Orders My Wishlist
`; signinLink.replaceWith(userDropdown); // Add dropdown toggle const dropdownBtn = document.getElementById("userDropdownBtn"); const dropdownMenu = document.getElementById("userDropdownMenu"); const logoutBtn = document.getElementById("logoutBtn"); if (dropdownBtn && dropdownMenu) { dropdownBtn.addEventListener("click", (e) => { e.stopPropagation(); dropdownMenu.classList.toggle("show"); }); // Close dropdown when clicking outside document.addEventListener("click", () => { dropdownMenu.classList.remove("show"); }); } if (logoutBtn) { logoutBtn.addEventListener("click", () => this.logout()); } } } else { // User is not logged in - ensure sign in link is present if (signinLink) { // Update to show sign in icon signinLink.innerHTML = ''; signinLink.href = "/signin"; signinLink.title = "Sign In"; } } // Add user dropdown styles if not present this.addStyles(); }, // Sync local cart with server when logged in async syncCartWithServer() { if (!this.isLoggedIn) return; try { // Get server cart const response = await fetch("/api/customers/cart", { credentials: "include", }); if (response.ok) { const data = await response.json(); if (data.success && data.items) { // Merge with local cart const localCart = JSON.parse( localStorage.getItem("skyart_cart") || "[]", ); // If local cart has items, push them to server for (const item of localCart) { const exists = data.items.find((i) => i.productId === item.id); if (!exists) { await this.addToServerCart(item); } } // Update local cart with server cart const mergedCart = data.items.map((item) => ({ id: item.productId, name: item.name, price: item.price, image: item.image, quantity: item.quantity, variantColor: item.variantColor, variantSize: item.variantSize, })); localStorage.setItem("skyart_cart", JSON.stringify(mergedCart)); if (window.AppState) { window.AppState.cart = mergedCart; window.AppState.updateUI(); } } } } catch (error) { console.error("[CustomerAuth] Cart sync failed:", error); } }, // Sync local wishlist with server async syncWishlistWithServer() { if (!this.isLoggedIn) return; try { const response = await fetch("/api/customers/wishlist", { credentials: "include", }); if (response.ok) { const data = await response.json(); if (data.success && data.items) { // Merge with local wishlist const localWishlist = JSON.parse( localStorage.getItem("wishlist") || "[]", ); // Push local items to server for (const item of localWishlist) { const exists = data.items.find((i) => i.productId === item.id); if (!exists) { await this.addToServerWishlist(item); } } // Update local wishlist with server data const mergedWishlist = data.items.map((item) => ({ id: item.productId, name: item.name, price: item.price, image: item.image, })); localStorage.setItem("wishlist", JSON.stringify(mergedWishlist)); if (window.AppState) { window.AppState.wishlist = mergedWishlist; window.AppState.updateUI(); } } } } catch (error) { console.error("[CustomerAuth] Wishlist sync failed:", error); } }, // Add item to server cart async addToServerCart(item) { try { await fetch("/api/customers/cart", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ productId: item.id, quantity: item.quantity || 1, variantColor: item.variantColor, variantSize: item.variantSize, }), }); } catch (error) { console.error("[CustomerAuth] Add to server cart failed:", error); } }, // Add item to server wishlist async addToServerWishlist(item) { try { await fetch("/api/customers/wishlist", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ productId: item.id }), }); } catch (error) { console.error("[CustomerAuth] Add to server wishlist failed:", error); } }, // Setup event listeners for cart/wishlist changes setupEventListeners() { // Listen for cart changes to sync with server window.addEventListener("cart-updated", async (e) => { if (this.isLoggedIn && e.detail) { // Debounce sync clearTimeout(this._syncCartTimeout); this._syncCartTimeout = setTimeout(() => { this.syncCartChanges(e.detail); }, 500); } }); // Listen for wishlist changes window.addEventListener("wishlist-updated", async (e) => { if (this.isLoggedIn && e.detail) { clearTimeout(this._syncWishlistTimeout); this._syncWishlistTimeout = setTimeout(() => { this.syncWishlistChanges(e.detail); }, 500); } }); }, // Sync cart changes with server async syncCartChanges(cart) { if (!this.isLoggedIn) return; // For now, just ensure items are added/removed // More sophisticated sync could be implemented }, // Sync wishlist changes with server async syncWishlistChanges(wishlist) { if (!this.isLoggedIn) return; // Sync wishlist changes }, // Logout async logout() { try { const response = await fetch("/api/customers/logout", { method: "POST", credentials: "include", }); if (response.ok) { this.user = null; this.isLoggedIn = false; // Show notification if (window.AppState && window.AppState.showNotification) { window.AppState.showNotification( "You have been signed out", "info", ); } // Redirect to home or refresh window.location.href = "/home"; } } catch (error) { console.error("[CustomerAuth] Logout failed:", error); } }, // Helper: Escape HTML escapeHtml(text) { if (!text) return ""; const div = document.createElement("div"); div.textContent = text; return div.innerHTML; }, // Add styles for user dropdown addStyles() { if (document.getElementById("customer-auth-styles")) return; const style = document.createElement("style"); style.id = "customer-auth-styles"; style.textContent = ` .user-dropdown { position: relative; } .user-btn { display: flex; align-items: center; gap: 6px; padding: 6px 12px !important; border-radius: 20px; background: var(--primary-pink-light, #fff5f7); transition: all 0.2s ease; } .user-btn:hover { background: var(--primary-pink, #ff85a2); color: white; } .user-btn i { font-size: 1.1rem; color: var(--primary-pink, #ff85a2); } .user-btn:hover i { color: white; } .user-name-short { font-size: 0.85rem; font-weight: 600; color: var(--text-primary, #333); max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .user-btn:hover .user-name-short { color: white; } .user-dropdown-menu { position: absolute; top: calc(100% + 10px); right: 0; min-width: 220px; background: white; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.15); opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.2s ease; z-index: 1000; overflow: hidden; } .user-dropdown-menu.show { opacity: 1; visibility: visible; transform: translateY(0); } .user-dropdown-header { display: flex; align-items: center; gap: 12px; padding: 16px; background: var(--primary-pink-light, #fff5f7); } .user-dropdown-header > i { font-size: 2rem; color: var(--primary-pink, #ff85a2); } .user-dropdown-header .user-info { display: flex; flex-direction: column; } .user-dropdown-header .welcome-text { font-size: 0.75rem; color: var(--text-light, #999); } .user-dropdown-header .user-name { font-size: 0.9rem; font-weight: 600; color: var(--text-primary, #333); } .user-dropdown-divider { height: 1px; background: var(--border-light, #eee); margin: 4px 0; } .user-dropdown-item { display: flex; align-items: center; gap: 10px; padding: 12px 16px; color: var(--text-primary, #333); text-decoration: none; font-size: 0.9rem; transition: all 0.2s ease; cursor: pointer; border: none; background: none; width: 100%; text-align: left; } .user-dropdown-item:hover { background: var(--primary-pink-light, #fff5f7); color: var(--primary-pink, #ff85a2); } .user-dropdown-item i { font-size: 1rem; width: 20px; text-align: center; } .logout-btn { color: #e74c3c; } .logout-btn:hover { background: #fdf2f2; color: #c0392b; } @media (max-width: 768px) { .user-name-short { display: none; } .user-btn { padding: 8px !important; border-radius: 50%; } .user-dropdown { -webkit-transform: translateZ(0); transform: translateZ(0); -webkit-backface-visibility: hidden; backface-visibility: hidden; } .user-dropdown-menu { right: -10px; min-width: 200px; position: fixed; top: 70px; right: 10px; -webkit-transform: translateZ(0); transform: translateZ(0); -webkit-backface-visibility: hidden; backface-visibility: hidden; will-change: opacity, visibility; } .user-dropdown-menu.show { transform: translateZ(0); } } `; document.head.appendChild(style); }, }; // Initialize on DOM ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { window.CustomerAuth.init(); }); } else { window.CustomerAuth.init(); } })();