EcomLib Product Details Page Feature Documentation


1. Prerequisites

Before starting, make sure you have:

2. Firebase Setup

Steps to configure Firebase Firestore:

  1. Go to Firebase Console and create a new project.
  2. Register a web app in your project and copy the Firebase config object:
const firebaseConfig = {
        apiKey: "AIzaSyCUad2MwdykedKqSWG7AOx9iIN5ePYxDg8",
        authDomain: "ecommerce-products-573ce.firebaseapp.com",
        projectId: "ecommerce-products-573ce",
        storageBucket: "ecommerce-products-573ce.firebasestorage.app",
        messagingSenderId: "875506405938",
        appId: "1:875506405938:web:815042d0daf81941ae622b",
        measurementId: "G-LTEMTLNB9E"
    };
  1. Enable Cloud Firestore in test mode to allow writing data during development.

3. Shop Product Template

Combine the HTML, CSS, and JS above into a single HTML file for immediate use in development. This template is ready to drop into Live Server and start uploading products.

<!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Product Details</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
        body {
            font-family: 'Poppins', sans-serif;
            background: #fafafa;
            margin: 0;
            padding: 0;
            color: #333;
            line-height: 1.6;
        }
        .product-details-container {
            width: 90%;
            margin: 20px auto;
            display: flex;
            gap: 60px;
            padding: 40px;
            border-radius: 0;
            position: relative;
        }
        .product-images {
            flex: 1;
            position: relative;
        }
        .main-image {
            width: 100%;
            height: 500px;
            border-radius: 0;
            object-fit: cover;
            background: #f8f8f8;
            transition: opacity 0.3s ease;
        }
        .thumbnails {
            display: flex;
            gap: 12px;
            margin-top: 20px;
            flex-wrap: wrap;
        }
        .thumbnail {
            width: 70px;
            height: 70px;
            border-radius: 0;
            cursor: pointer;
            object-fit: cover;
            border: 1px solid #e0e0e0;
            transition: all 0.3s ease;
            opacity: 0.7;
        }
        .thumbnail:hover,
        .thumbnail.active {
            border-color: #b8860b;
            opacity: 1;
            transform: scale(1.05);
        }
        .product-info {
            flex: 1;
            padding-top: 20px;
        }
        .product-info h1 {
            font-size: 32px;
            font-weight: 300;
            letter-spacing: 1px;
            margin-bottom: 10px;
            color: #1a1a1a;
        }
        .price {
            color: #b8860b;
            font-size: 28px;
            font-weight: 400;
            margin-top: 0;
            margin-bottom: 25px;
            letter-spacing: 0.5px;
        }
        .product-info p {
            font-size: 15px;
            color: #666;
            margin-bottom: 25px;
        }
        .product-info h3 {
            font-size: 18px;
            font-weight: 500;
            color: #1a1a1a;
            margin-bottom: 15px;
            letter-spacing: 0.5px;
            padding-bottom: 8px;
        }
        .product-info ul {
            padding-left: 20px;
            margin-bottom: 30px;
        }
        .product-info li {
            margin-bottom: 8px;
            color: #666;
            font-size: 15px;
        }
        .product-info li:before {
            content: "•";
            color: #b8860b;
            font-weight: bold;
            display: inline-block;
            width: 1em;
            margin-left: -1em;
        }
        #productSpecifications p {
            margin-bottom: 10px;
            font-size: 15px;
        }
        #productSpecifications strong {
            color: #1a1a1a;
            font-weight: 500;
            min-width: 120px;
            display: inline-block;
        }
        .add-to-cart-btn {
            padding: 15px 20px;
            background: #1a1a1a;
            color: white;
            border-radius: 0;
            border: none;
            cursor: pointer;
            margin-top: 30px;
            font-size: 14px;
            font-weight: 500;
            letter-spacing: 1px;
            text-transform: uppercase;
            transition: all 0.3s ease;
            width: 250px;
        }
        .add-to-cart-btn:hover {
            background: #b8860b;
            transform: translateY(-2px);
        }
        .product-info h3:after {
            content: '';
            display: block;
            width: 40px;
            height: 1px;
            background: #b8860b;
            margin-top: 8px;
        }
        #productName:empty:before {
            content: "Loading...";
            color: #999;
        }
        /* Related Products */
        .container-product-details {
            width: 90%;
            max-width: 1200px;
            margin: 0 auto;
            padding: 15px;
        }
        .products-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 2rem;
        }
        .product-card {
            background-color: #ffffff;
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
            transition: all 0.3s ease;
        }
        .product-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
        }
        .product-card img {
            width: 100%;
            height: 200px;
            object-fit: cover;
        }
        .product-card-content {
            padding: 1.5rem;
        }
        .product-card h3 {
            font-size: 1.2rem;
            margin-bottom: 0.5rem;
        }
        .product-card-price {
            color:#d4af37;
            font-weight: 600;
            margin-bottom: 1rem;
        }
        .product-card .btn {
            width: 100%;
        }
        .btn-secondary {
            display: inline-block;
            padding: 15px 20px;
            width: 250px;
            border: none;
            border-radius: 4px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 1px;
            cursor: pointer;
            transition: all 0.3s ease;
            background-color: #fff;
            color: #121212;
            border: 2px solid #b8860b;
        }
        @media (max-width:768px) {
            .product-details-container {
                flex-direction: column;
                width: 98%;
            }
        }
    </style>
    </head>
    <body>

    <div class="product-details-container">

        <!-- Images -->
        <div class="product-images">
            <img id="mainImage" class="main-image" src="" alt="Product Image">

            <div class="thumbnails" id="thumbnails">
                <!-- Thumbnails injected here -->
            </div>

        </div>

        <!-- Info -->
        <div class="product-info">
            <h1 id="productName">Loading...</h1>
            <p class="price">Ksh <span id="productPrice" style="color: #b8860b;font-size: 1.6em;">0</span></p>

            <h3>About this Product</h3>
            <p id="productDescription"></p>

            <h3>Specifications</h3>
            <div id="productSpecifications"></div>

            <button class="btn btn-secondary" id="buyNow">
                <i class="fab fa-whatsapp"></i> Order Now
            </button>

            <button class="add-to-cart-btn">ADD TO CART</button>

            <br>
        </div>

    </div>

    <!-- Related Products -->
    <div class="products-grid" id="relatedProducts" style="padding:20px"></div>

    <script type="module">
    import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.0/firebase-app.js";
    import { getFirestore, doc, getDoc, collection, getDocs } from "https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js";

    const firebaseConfig = {
        apiKey: "AIzaSyCUad2MwdykedKqSWG7AOx9iIN5ePYxDg8",
        authDomain: "ecommerce-products-573ce.firebaseapp.com",
        projectId: "ecommerce-products-573ce",
        storageBucket: "ecommerce-products-573ce.firebasestorage.app",
        messagingSenderId: "875506405938",
        appId: "1:875506405938:web:815042d0daf81941ae622b",
        measurementId: "G-LTEMTLNB9E"
    };

    const app = initializeApp(firebaseConfig);
    const db = getFirestore(app);

    const productId = new URLSearchParams(window.location.search).get("id");

    function shuffleArray(array) {
        return array.sort(() => Math.random() - 0.5);
    }

    async function loadProduct() {
        if (!productId) {
            document.body.innerHTML = "<h2>⚠ Product ID missing in URL.</h2>";
            return;
        }

        const docRef = doc(db, "products", productId);
        const docSnap = await getDoc(docRef);

        if (!docSnap.exists()) {
            document.body.innerHTML = "<h2>⚠ Product not found.</h2>";
            return;
        }

        const product = docSnap.data();

        document.getElementById("productName").textContent = product.name ?? "Unnamed Product";
        document.getElementById("productPrice").textContent = product.price ?? "0";
        document.getElementById("productDescription").textContent = product.productDescription || "No description available.";

        const mainImage = document.getElementById("mainImage");
        mainImage.src = product.mainImage || "";

        const thumbnailsDiv = document.getElementById("thumbnails");
        thumbnailsDiv.innerHTML = "";

        if (product.supportImages?.length > 0) {
            product.supportImages.forEach(imgUrl => {
                const img = document.createElement("img");
                img.src = imgUrl;
                img.classList.add("thumbnail");
                img.onclick = () => (mainImage.src = imgUrl);
                thumbnailsDiv.appendChild(img);
            });
        }

        const specsDiv = document.getElementById("productSpecifications");
        specsDiv.innerHTML = "";
        specsDiv.style.display = "flex";
        specsDiv.style.flexWrap = "wrap";
        specsDiv.style.gap = "15px";

        if (product.specifications && typeof product.specifications === "object") {
            Object.entries(product.specifications).forEach(([key, value]) => {
                const specItem = document.createElement("div");
                specItem.style.flex = "1 1 200px";
                specItem.style.background = "#f4f4f4";
                specItem.style.padding = "10px 12px";
                specItem.style.borderRadius = "8px";
                specItem.style.boxShadow = "0 2px 6px rgba(0,0,0,0.1)";
                specItem.innerHTML = `<strong style="text-transform: capitalize;">${key}:</strong> ${value}`;
                specsDiv.appendChild(specItem);
            });
        }

        loadRelatedProducts();
    }

    async function loadRelatedProducts() {
        const relatedContainer = document.getElementById("relatedProducts");
        if (!relatedContainer) return;

        const querySnapshot = await getDocs(collection(db, "products"));
        let products = [];
        querySnapshot.forEach(doc => {
            if (doc.id !== productId) {
                products.push({ id: doc.id, ...doc.data() });
            }
        });

        const selected = shuffleArray(products).slice(0, 4);
        relatedContainer.innerHTML = "";

        selected.forEach(product => {
            relatedContainer.innerHTML += `
                <div class="product-card">
                    <img src="${product.mainImage}" alt="${product.name}">
                    <div class="product-card-content">
                        <h3>${product.name}</h3>
                        <p class="product-card-price">Ksh ${product.price}</p>
                        <button class="btn btn-secondary" onclick="window.location.href='product.html?id=${product.id}'">
                            View Details
                        </button>
                    </div>
                </div>
            `;
        });
    }

    loadProduct();
    </script>

    </body>
    </html>
    
All code blocks above are fully copyable. Follow the Firebase and Cloudinary setup carefully to ensure uploads work correctly.