Repository: ResonanceSE/Resonance
Pull request #23: Reconstruct folder , add detail page
Author: login: KKhamwiset
Labels: name: enhancement
description: New feature or request
Created: 2025-03-22T20:54:29Z | Merged: 2025-03-22T21:21:50Z



Diff:
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 4769cb6..8b71ff1 100644
Binary files a/backend/requirements.txt and b/backend/requirements.txt differ
diff --git a/backend/server/controller/product_controller.py b/backend/server/controller/product_controller.py
index 23a1b20..1f58f42 100644
--- a/backend/server/controller/product_controller.py
+++ b/backend/server/controller/product_controller.py
@@ -1,3 +1,4 @@
+from django.db.models import Count
 from django.shortcuts import get_object_or_404
 from rest_framework.decorators import api_view
 from rest_framework.response import Response
@@ -18,9 +19,122 @@ def get_all_products(request):
     return Response(serializer.data)
 
 
-# Get product by name
 @api_view(["GET"])
-def get_product_by_name(request, name):
-    product = get_object_or_404(Product, name__iexact=name)
+def get_product_by_category(request, category):
+    print(category)
+    products = Product.objects.filter(category="DefaultType")
+    serializer = ProductSerializer(products, many=True)
+    return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_detailed(request, category, id):
+    product = get_object_or_404(Product, pk=id)
     serializer = ProductSerializer(product)
     return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_detailed_single_route(request, id):
+    product = get_object_or_404(Product, pk=id)
+    serializer = ProductSerializer(product)
+    return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_filters(request):
+    """
+    Get unique filter options for the product filters
+    Returns all unique brands, connection types, etc. from the products database
+    """
+    # Get unique brands and count of products for each
+    brands = (
+        Product.objects.values("brand").annotate(count=Count("brand")).order_by("brand")
+    )
+
+    # Get unique connection types and count of products for each
+    connections = (
+        Product.objects.values("connections")
+        .annotate(count=Count("connections"))
+        .order_by("connections")
+    )
+
+    # Get price ranges (calculated on the fly)
+    price_min = (
+        Product.objects.order_by("price").first().price
+        if Product.objects.exists()
+        else 0
+    )
+    price_max = (
+        Product.objects.order_by("-price").first().price
+        if Product.objects.exists()
+        else 1000
+    )
+
+    # Create price ranges based on actual data
+    price_ranges = []
+    if Product.objects.exists():
+        # Define price ranges
+        if price_min < 100:
+            price_ranges.append(
+                {
+                    "name": "Under $100",
+                    "min": 0,
+                    "max": 99.99,
+                    "count": Product.objects.filter(price__lt=100).count(),
+                }
+            )
+
+        if price_min < 300 and price_max > 100:
+            price_ranges.append(
+                {
+                    "name": "$100 - $300",
+                    "min": 100,
+                    "max": 299.99,
+                    "count": Product.objects.filter(
+                        price__gte=100, price__lt=300
+                    ).count(),
+                }
+            )
+
+        if price_min < 500 and price_max > 300:
+            price_ranges.append(
+                {
+                    "name": "$300 - $500",
+                    "min": 300,
+                    "max": 499.99,
+                    "count": Product.objects.filter(
+                        price__gte=300, price__lt=500
+                    ).count(),
+                }
+            )
+
+        if price_max > 500:
+            price_ranges.append(
+                {
+                    "name": "Over $500",
+                    "min": 500,
+                    "max": None,
+                    "count": Product.objects.filter(price__gte=500).count(),
+                }
+            )
+    else:
+        # Default price ranges if no products exist
+        price_ranges = [
+            {"name": "Under $100", "min": 0, "max": 99.99, "count": 0},
+            {"name": "$100 - $300", "min": 100, "max": 299.99, "count": 0},
+            {"name": "$300 - $500", "min": 300, "max": 499.99, "count": 0},
+            {"name": "Over $500", "min": 500, "max": None, "count": 0},
+        ]
+
+    # Product types (you could extend your model to include a 'type' field)
+    # For now, let's return some default types based on product descriptions
+
+    return Response(
+        {
+            "brands": list(brands),
+            "connections": list(connections),
+            "types": [],
+            "price_ranges": price_ranges,
+        }
+    )
diff --git a/backend/server/migrations/0006_product_category_product_image_url.py b/backend/server/migrations/0006_product_category_product_image_url.py
new file mode 100644
index 0000000..37e98c6
--- /dev/null
+++ b/backend/server/migrations/0006_product_category_product_image_url.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1.4 on 2025-03-22 17:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("server", "0005_product_brand_product_connections"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="product",
+            name="category",
+            field=models.CharField(default="DefaultType", max_length=255),
+        ),
+        migrations.AddField(
+            model_name="product",
+            name="image_url",
+            field=models.CharField(blank=True, max_length=500, null=True),
+        ),
+    ]
diff --git a/backend/server/models/product_model.py b/backend/server/models/product_model.py
index 4e6a29f..53b70af 100644
--- a/backend/server/models/product_model.py
+++ b/backend/server/models/product_model.py
@@ -4,11 +4,13 @@
 class Product(models.Model):
     name = models.CharField(max_length=255)
     description = models.TextField()
+    category = models.CharField(max_length=255, default="DefaultType")
     brand = models.CharField(max_length=255, default="DefaultBrand")
     connections = models.CharField(max_length=255, default="DefaultConnections")
     price = models.DecimalField(max_digits=10, decimal_places=2)
     stock = models.IntegerField(default=0)
     created_at = models.DateTimeField(auto_now_add=True)
+    image_url = models.CharField(max_length=500, blank=True, null=True)
 
     def __str__(self):
         return self.name
diff --git a/backend/server/urls.py b/backend/server/urls.py
index f93729c..cf85aee 100644
--- a/backend/server/urls.py
+++ b/backend/server/urls.py
@@ -17,7 +17,13 @@
 
 from django.contrib import admin
 from django.urls import path
-from server.controller.product_controller import get_all_products, get_product_by_name
+from server.controller.product_controller import (
+    get_product_detailed_single_route,
+    get_all_products,
+    get_product_filters,
+    get_product_by_category,
+    get_product_detailed,
+)
 from .controller.auth_controller import (
     RegisterAPI,
     LoginAPI,
@@ -31,8 +37,16 @@
     path("", home_view),
     path("keepalive/", keep_alive),
     path("admin/", admin.site.urls),
+    # Product api urls
+    path("api/products/filters/", get_product_filters, name="product-filters"),
+    path("api/products/<str:category>/", get_product_by_category, name="category"),
+    path("api/products/<int:id>/", get_product_detailed_single_route, name="product"),
+    path(
+        "api/products/<str:category>/<int:id>/",
+        get_product_detailed,
+        name="product-detail",
+    ),
     path("api/products/", get_all_products, name="all-products"),
-    path("api/products/<str:name>/", get_product_by_name, name="product-by-name"),
     # Auth api urls
     path("api/auth/register/", RegisterAPI.as_view()),
     path("api/auth/login/", LoginAPI.as_view()),
diff --git a/frontend/layouts/product_detail_layout.vue b/frontend/layouts/product_detail_layout.vue
new file mode 100644
index 0000000..098659f
--- /dev/null
+++ b/frontend/layouts/product_detail_layout.vue
@@ -0,0 +1,529 @@
+<script setup>
+const route = useRoute();
+const productId = route.params.id || route.query.id;
+const category = route.params.category || extractCategoryFromPath(route.path) || route.query.category;
+
+function extractCategoryFromPath(path) {
+  const parts = path.split('/');
+  if (parts.length >= 3 && parts[1] === 'products') {
+    return parts[2];
+  }
+  return null;
+}
+
+console.log("Product detail params:", { category, productId, path: route.path });
+const apiUrl = useRuntimeConfig().public.apiUrl || 'http://localhost:8000';
+
+// Core state
+const product = ref(null);
+const isLoading = ref(true);
+const error = ref(null);
+const addToCartSuccess = ref(false);
+const selectedImage = ref(0);
+const quantity = ref(1);
+
+// Simple breadcrumbs
+const breadcrumbs = computed(() => [
+  { name: 'Home', path: '/' },
+  { name: 'Products', path: '/products' },
+  { name: capitalizeFirstLetter(category), path: `/products/${category}` },
+  { name: product.value?.name || 'Product Details', path: '' }
+]);
+
+// Compute props for product page layout
+const layoutProps = computed(() => {
+  if (!product.value) return {};
+  
+  return {
+    brand: product.value.brand || '',
+    name: product.value.name || '',
+    price: product.value.price || 0,
+    mainImage: product.value.image_url || product.value.images?.[selectedImage.value] || `https://via.placeholder.com/500x500?text=Product+Image`,
+    images: product.value.images || [],
+    colors: product.value.colors || [],
+    description: product.value.description || '',
+    features: product.value.features || [],
+    stock: product.value.stock || 0
+  };
+});
+
+// Utility functions
+const capitalizeFirstLetter = (string) => {
+  if (!string) return '';
+  return string.charAt(0).toUpperCase() + string.slice(1);
+};
+
+// Format currency
+const formatCurrency = (price) => {
+  // Convert string to number if needed
+  const numericPrice = typeof price === 'string' ? parseFloat(price) : price;
+  
+  return new Intl.NumberFormat('en-US', {
+    style: 'currency',
+    currency: 'USD'
+  }).format(numericPrice || 0);
+};
+
+// Select an image from thumbnails
+const selectImage = (index) => {
+  selectedImage.value = index;
+};
+
+// Change quantity
+const decreaseQuantity = () => {
+  if (quantity.value > 1) {
+    quantity.value--;
+  }
+};
+
+const increaseQuantity = () => {
+  if (quantity.value < 10) {
+    quantity.value++;
+  }
+};
+
+// Add to cart handler
+const handleAddToCart = () => {
+  if (!product.value) return;
+  
+  // You can get the cart from local storage or state management
+  const cart = JSON.parse(localStorage.getItem('cart') || '[]');
+  
+  // Check if product already exists in cart
+  const existingProductIndex = cart.findIndex(item => 
+    item.id === product.value.id && 
+    item.category === product.value.category
+  );
+  
+  if (existingProductIndex >= 0) {
+    // Update quantity if product already in cart
+    cart[existingProductIndex].quantity += quantity.value;
+  } else {
+    // Add new product to cart
+    cart.push({
+      id: product.value.id,
+      name: product.value.name,
+      price: product.value.price,
+      image: product.value.images?.[0] || '',
+      category: product.value.category,
+      quantity: quantity.value
+    });
+  }
+  
+  // Save updated cart
+  localStorage.setItem('cart', JSON.stringify(cart));
+  
+  // Show success message
+  addToCartSuccess.value = true;
+  setTimeout(() => {
+    addToCartSuccess.value = false;
+  }, 2000);
+};
+
+// Fetch product data
+const fetchProduct = async () => {
+  isLoading.value = true;
+  error.value = null;
+
+  if (!category || !productId) {
+    console.error('Missing required parameters for product fetch');
+    error.value = 'Invalid product URL. Missing category or ID.';
+    isLoading.value = false;
+  }
+
+  console.log(`Fetching product: ${apiUrl}/api/products/${category}/${productId}`);
+  try {
+    const response = await fetch(`${apiUrl}/api/products/${category}/${productId}`);
+    
+    if (!response.ok) {
+      throw new Error(`Failed to fetch product: ${response.status}`);
+    }
+    
+    const data = await response.json();
+    product.value = data;
+
+    selectedImage.value = 0;
+    quantity.value = 1;
+  } catch (err) {
+    console.error('Error fetching product:', err);
+    error.value = 'Failed to load product details. Please try again.';
+    
+    // Use mock data during development
+    if (process.env.NODE_ENV !== 'production') {
+      product.value = getMockProduct();
+    }
+  } finally {
+    isLoading.value = false;
+  }
+};
+
+// Mock product data for development
+const getMockProduct = () => {
+  return {
+    id: productId,
+    name: `Premium ${category.charAt(0).toUpperCase() + category.slice(1, -1)}`,
+    brand: "Resonance",
+    category: category,
+    price: 349.99,
+    description: `Experience premium audio quality with our ${category} line. Designed for comfort and exceptional sound performance.`,
+    stock: 35,
+    images: [
+      `https://via.placeholder.com/500x500?text=${category}+Main`,
+      `https://via.placeholder.com/500x500?text=${category}+Side`,
+      `https://via.placeholder.com/500x500?text=${category}+Top`
+    ],
+    features: [
+      "Premium sound quality",
+      "30-hour battery life",
+      "Comfortable design"
+    ],
+    colors: [
+      { value: "black", label: "Black", hex: "#000000" },
+      { value: "silver", label: "Silver", hex: "#C0C0C0" },
+      { value: "blue", label: "Midnight Blue", hex: "#191970" }
+    ]
+  };
+};
+
+watch(() => route.fullPath, (newPath, oldPath) => {
+  if (newPath !== oldPath) {
+    console.log('Route changed, fetching new product data');
+    fetchProduct();
+  }
+}, { immediate: true });
+
+let initialFetchDone = false;
+onMounted(() => {
+  if (!initialFetchDone) {
+    initialFetchDone = true;
+    fetchProduct();
+  }
+});
+
+// Provide data to child components
+provide('product', product);
+provide('isLoading', isLoading);
+provide('error', error);
+provide('layoutProps', layoutProps);
+provide('handleAddToCart', handleAddToCart);
+provide('formatCurrency', formatCurrency);
+</script>
+
+<template>
+  <div>
+    <!-- Header -->
+    <NavbarHeader/>
+    
+    <div class="bg-gray-50">
+      <!-- Loading state -->
+      <div v-if="isLoading" class="container mx-auto px-4 py-24 text-center">
+        <div class="flex flex-col items-center justify-center">
+          <div class="loading-spinner"/>
+          <p class="mt-4 text-gray-500">Loading product details...</p>
+        </div>
+      </div>
+      
+      <!-- Error state -->
+      <div v-else-if="error" class="container mx-auto px-4 py-16">
+        <div class="bg-white rounded-lg shadow-md p-8 max-w-2xl mx-auto">
+          <div class="flex items-center text-red-500 mb-4">
+            <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 mr-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
+            </svg>
+            <h2 class="text-xl font-bold">Error Loading Product</h2>
+          </div>
+          <p class="text-gray-600 mb-6">{{ error }}</p>
+          <button class="btn btn-primary" @click="fetchProduct">
+            <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
+            </svg>
+            Try Again
+          </button>
+        </div>
+      </div>
+      
+      <!-- Product display -->
+      <div v-else-if="product" class="container mx-auto px-4 py-8">
+        <!-- Breadcrumbs -->
+        <div class="mb-6 hidden md:block">
+          <div class="flex items-center text-sm text-gray-500">
+            <NuxtLink
+v-for="(crumb, index) in breadcrumbs.slice(0, -1)" :key="index" :to="crumb.path" 
+              class="hover:text-orange-500 transition-colors">
+              {{ crumb.name }}
+              <span v-if="index < breadcrumbs.length - 2" class="mx-2">/</span>
+            </NuxtLink>
+            <span class="mx-2 text-gray-300">/</span>
+            <span class="text-gray-700 font-medium">{{ breadcrumbs[breadcrumbs.length - 1].name }}</span>
+          </div>
+        </div>
+        
+        <!-- Main product section -->
+        <div class="bg-white rounded-xl shadow-sm overflow-hidden">
+          <div class="grid grid-cols-1 md:grid-cols-2 gap-0">
+            <!-- Product Images Section -->
+            <div class="bg-gray-50 p-6 flex flex-col">
+              <!-- Main image with zoom effect -->
+              <div class="relative bg-white rounded-lg overflow-hidden mb-4 h-80 md:h-96">
+                <img 
+                  :src="layoutProps.images[selectedImage]" 
+                  :alt="layoutProps.name" 
+                  class="w-full h-full object-contain hover:scale-105 transition-transform duration-500"
+                >
+              </div>
+              
+              <!-- Thumbnails -->
+              <div class="flex gap-3 overflow-x-auto pb-2">
+                <button 
+                  v-for="(image, index) in layoutProps.images" 
+                  :key="index" 
+                  class="w-20 h-20 p-1 rounded-md flex-shrink-0 transition-all duration-200 overflow-hidden"
+                  :class="selectedImage === index ? 'ring-2 ring-orange-500 bg-white shadow-md' : 'bg-white hover:shadow-md'"
+                  @click="selectImage(index)"
+                >
+                  <img 
+                    :src="image" 
+                    :alt="`${layoutProps.name} thumbnail ${index + 1}`" 
+                    class="w-full h-full object-contain"
+                  >
+                </button>
+              </div>
+            </div>
+            
+            <!-- Product Details Section -->
+            <div class="p-6 md:p-8 flex flex-col">
+              <!-- Brand and Name -->
+              <div class="mb-6">
+                <div class="inline-block px-3 py-1 bg-gray-100 text-gray-600 text-xs font-medium rounded-full mb-2">
+                  {{ layoutProps.brand }}
+                </div>
+                <h1 class="text-2xl md:text-3xl font-bold text-gray-900">{{ layoutProps.name }}</h1>
+              </div>
+              
+              <!-- Price -->
+              <div class="text-3xl font-bold text-orange-500 mb-6">
+                {{ formatCurrency(layoutProps.price) }}
+              </div>
+              
+              <!-- Description -->
+              <div class="mb-6">
+                <h2 class="text-lg font-semibold mb-2 text-gray-900">Description</h2>
+                <p class="text-gray-700 leading-relaxed">{{ layoutProps.description }}</p>
+              </div>
+              
+              <!-- Colors -->
+              <div v-if="layoutProps.colors && layoutProps.colors.length > 0" class="mb-6">
+                <h2 class="text-lg font-semibold mb-3 text-gray-900">Colors</h2>
+                <div class="flex flex-wrap gap-3">
+                  <div 
+                    v-for="color in layoutProps.colors" 
+                    :key="color.value"
+                    class="relative w-10 h-10 rounded-full cursor-pointer border-2 border-white shadow-sm hover:shadow-md transition-shadow"
+                    :style="`background-color: ${color.hex}`"
+                    :title="color.label"
+                  >
+                    <span class="sr-only">{{ color.label }}</span>
+                  </div>
+                </div>
+              </div>
+              
+              <!-- Features -->
+              <div class="mb-6">
+                <h2 class="text-lg font-semibold mb-3 text-gray-900">Features</h2>
+                <ul class="space-y-2">
+                  <li v-for="(feature, index) in layoutProps.features" :key="index" class="flex items-start">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
+                    </svg>
+                    <span class="text-gray-700">{{ feature }}</span>
+                  </li>
+                </ul>
+              </div>
+              
+              <!-- Quantity and Add to Cart -->
+              <div class="mt-auto">
+                <div class="flex flex-col sm:flex-row sm:items-center gap-4 mb-4">
+                  <!-- Stock status -->
+                  <div v-if="layoutProps.stock > 0" class="flex items-center text-green-600 text-sm">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
+                    </svg>
+                    In Stock
+                  </div>
+                  <div v-else class="flex items-center text-red-500 text-sm">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
+                    </svg>
+                    Out of Stock
+                  </div>
+                  
+                  <!-- Quantity selector -->
+                  <div class="join border border-gray-300 rounded-lg">
+                    <button 
+                      class="join-item btn btn-sm btn-ghost text-gray-700 px-3" 
+                      :disabled="quantity <= 1"
+                      @click="decreaseQuantity"
+                    >
+                      <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
+                      </svg>
+                    </button>
+                    <div class="join-item flex items-center justify-center w-10 bg-white text-gray-800">
+                      {{ quantity }}
+                    </div>
+                    <button 
+                      class="join-item btn btn-sm btn-ghost text-gray-700 px-3" 
+                      :disabled="quantity >= 10"
+                      @click="increaseQuantity"
+                    >
+                      <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
+                      </svg>
+                    </button>
+                  </div>
+                </div>
+                
+                <!-- Add to cart button -->
+                <button 
+                  class="btn btn-primary btn-lg w-full group"
+                  :disabled="layoutProps.stock <= 0"
+                  @click="handleAddToCart"
+                >
+                  <span class="flex items-center">
+                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 group-hover:animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
+                    </svg>
+                    {{ layoutProps.stock > 0 ? 'Add to Cart' : 'Out of Stock' }}
+                  </span>
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- Additional product information -->
+        <div class="mt-12 bg-white rounded-xl shadow-sm overflow-hidden">
+          <div class="tabs tabs-bordered">
+            <a class="tab tab-bordered tab-active">Details</a>
+            <a class="tab tab-bordered">Specifications</a>
+          </div>
+          <div class="p-6">
+            <div class="prose max-w-none">
+              <p>{{ layoutProps.description }}</p>
+              
+              <h3>Key Features</h3>
+              <ul>
+                <li v-for="(feature, index) in layoutProps.features" :key="index">
+                  {{ feature }}
+                </li>
+              </ul>
+            </div>
+          </div>
+        </div>
+        
+        <div class="mt-12">
+          <slot/>
+        </div>
+      </div>
+      
+      <!-- No product found state -->
+      <div v-else class="container mx-auto px-4 py-16 text-center">
+        <div class="bg-white rounded-lg shadow-md p-8 max-w-2xl mx-auto">
+          <svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto text-gray-400 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
+          </svg>
+          <h2 class="text-2xl font-bold mb-2">Product Not Found</h2>
+          <p class="text-gray-600 mb-6">We couldn't find the product you're looking for. It may have been removed or the URL might be incorrect.</p>
+          
+          <div class="flex flex-col sm:flex-row gap-4 justify-center">
+            <NuxtLink :to="`/products/${category}`" class="btn btn-primary">
+              Browse {{ capitalizeFirstLetter(category) }}
+            </NuxtLink>
+            <NuxtLink to="/products" class="btn btn-outline">
+              View All Products
+            </NuxtLink>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- Mobile sticky add to cart -->
+    <div v-if="product && !isLoading" class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 p-3 md:hidden z-10">
+      <div class="flex items-center justify-between">
+        <div>
+          <div class="text-lg font-bold text-orange-500">{{ formatCurrency(layoutProps.price) }}</div>
+          <div v-if="layoutProps.stock > 0" class="text-xs text-green-600">In Stock</div>
+          <div v-else class="text-xs text-red-500">Out of Stock</div>
+        </div>
+        <button 
+          class="btn btn-primary"
+          :disabled="layoutProps.stock <= 0"
+          @click="handleAddToCart"
+        >
+          Add to Cart
+        </button>
+      </div>
+    </div>
+    
+    <!-- Add to cart success toast -->
+    <div v-if="addToCartSuccess" class="toast toast-end z-50">
+      <div class="alert alert-success shadow-lg">
+        <div class="flex items-center">
+          <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
+          </svg>
+          <div>
+            <h3 class="font-bold">Added to Cart!</h3>
+            <div class="text-xs">{{ quantity }} × {{ layoutProps.name }}</div>
+          </div>
+        </div>
+        <div class="flex-none">
+          <NuxtLink to="/cart" class="btn btn-sm btn-ghost">View Cart</NuxtLink>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.loading-spinner {
+  display: inline-block;
+  width: 50px;
+  height: 50px;
+  border: 3px solid rgba(249, 115, 22, 0.2);
+  border-radius: 50%;
+  border-top-color: rgba(249, 115, 22, 1);
+  animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+/* Add hover effect to product thumbnails */
+.thumbnail-hover:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+}
+
+/* Smooth transitions */
+.btn, .btn-primary, img {
+  transition: all 0.3s ease;
+}
+
+/* Group hover animation for add to cart icon */
+.group:hover .group-hover\:animate-bounce {
+  animation: bounce 1s infinite;
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(-5%);
+    animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
+  }
+  50% {
+    transform: translateY(0);
+    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+  }
+}
+</style>
\ No newline at end of file
diff --git a/frontend/layouts/product_layout.vue b/frontend/layouts/product_layout.vue
index 5af4171..6b4c080 100644
--- a/frontend/layouts/product_layout.vue
+++ b/frontend/layouts/product_layout.vue
@@ -1,5 +1,5 @@
 <script setup>
-import { FooterSection, NavbarHeader } from '#components';
+
 
 // Shared layout state
 const pageTitle = inject('pageTitle', ref('Products'));
@@ -7,6 +7,107 @@ const pageTitle = inject('pageTitle', ref('Products'));
 // Mobile filter modal state
 const filterModalOpen = ref(false);
 
+const apiUrl = useRuntimeConfig().public.apiUrl;
+
+// Fetch filter options from API instead of using hardcoded values
+const isLoadingFilters = ref(true);
+const filtersError = ref(null);
+
+// Filter data that will be populated from the API
+const brands = ref([]);
+const types = ref([]);
+const connections = ref([]);
+const priceRanges = ref([]);
+
+// Fetch filters from API
+const fetchFilters = async () => {
+  isLoadingFilters.value = true;
+  filtersError.value = null;
+  
+  try {
+    const response = await fetch(`${apiUrl}/api/products/filters/`);
+    
+    if (!response.ok) {
+      throw new Error(`Failed to fetch filters: ${response.statusText}`);
+    }
+    
+    const data = await response.json();
+    
+    // Convert API response to the format our UI needs
+    brands.value = data.brands.map(brand => ({
+      name: brand.brand,
+      selected: false,
+      count: brand.count
+    }));
+    
+    types.value = data.types.map(type => ({
+      name: type.name,
+      selected: false,
+      count: type.count
+    }));
+    
+    connections.value = data.connections.map(conn => ({
+      name: conn.connections,
+      selected: false,
+      count: conn.count
+    }));
+    
+    priceRanges.value = data.price_ranges.map(range => ({
+      name: range.name,
+      selected: false,
+      count: range.count,
+      min: range.min,
+      max: range.max
+    }));
+    
+  } catch (error) {
+    console.error('Error fetching filters:', error);
+    filtersError.value = error.message;
+    
+    if (brands.value.length === 0) {
+      brands.value = [
+        { name: 'Sony', selected: false },
+        { name: 'Bose', selected: false },
+        { name: 'JBL', selected: false },
+        { name: 'Sonos', selected: false },
+        { name: 'Harman Kardon', selected: false },
+      ];
+    }
+    
+    if (types.value.length === 0) {
+      types.value = [
+        { name: 'Portable', selected: false },
+        { name: 'Desktop', selected: false },
+        { name: 'Bookshelf', selected: false },
+        { name: 'Floor Standing', selected: false },
+      ];
+    }
+    
+    if (connections.value.length === 0) {
+      connections.value = [
+        { name: 'Bluetooth', selected: false },
+        { name: 'Wireless', selected: false },
+        { name: 'Wired', selected: false },
+        { name: 'USB-C', selected: false },
+      ];
+    }
+    
+    if (priceRanges.value.length === 0) {
+      priceRanges.value = [
+        { name: 'Under $100', selected: false },
+        { name: '$100 - $300', selected: false },
+        { name: '$300 - $500', selected: false },
+        { name: 'Over $500', selected: false },
+      ];
+    }
+  } finally {
+    isLoadingFilters.value = false;
+  }
+};
+
+onMounted(() => {
+  fetchFilters();
+});
 // Expandable sections state in the modal
 const modalBrandExpanded = ref(true);
 const modalTypeExpanded = ref(true);
@@ -43,36 +144,7 @@ const getSortLabel = computed(() => {
   }
 });
 
-// Brand filter options
-const brands = ref([
-  { name: 'Sony', selected: false },
-  { name: 'Bose', selected: false },
-  { name: 'JBL', selected: false },
-  { name: 'Sonos', selected: false },
-  { name: 'Harman Kardon', selected: false },
-]);
-
-// Type filter options
-const types = ref([
-  { name: 'Portable', selected: false },
-  { name: 'Desktop', selected: false },
-  { name: 'Bookshelf', selected: false },
-  { name: 'Floor Standing', selected: false },
-]);
-
-const connections = ref([
-  { name: 'Bluetooth', selected: false },
-  { name: 'Wireless', selected: false },
-  { name: 'Wired', selected: false },
-  { name: 'USB-C', selected: false },
-]);
-
-const priceRanges = ref([
-  { name: 'Under $100', selected: false },
-  { name: '$100 - $300', selected: false },
-  { name: '$300 - $500', selected: false },
-  { name: 'Over $500', selected: false },
-]);
+
 
 // Helper getters for active filters display
 const getSelectedBrands = computed(() => 
diff --git a/frontend/layouts/product_page_layout.vue b/frontend/layouts/product_page_layout.vue
deleted file mode 100644
index 88a2bb5..0000000
--- a/frontend/layouts/product_page_layout.vue
+++ /dev/null
@@ -1,252 +0,0 @@
-<script setup>
-// Props for layout
-const props = defineProps({
-  brandName: {
-    type: String,
-    default: ''
-  },
-  modelName: {
-    type: String,
-    default: ''
-  },
-  productSubtitle: {
-    type: String,
-    default: ''
-  },
-  productPrice: {
-    type: [Number, String],
-    default: 0
-  },
-  mainImage: {
-    type: String,
-    default: ''
-  },
-  productImages: {
-    type: Array,
-    default: () => []
-  },
-  productColors: {
-    type: Array,
-    default: () => []
-  },
-  productDescription: {
-    type: String,
-    default: ''
-  },
-  productFeatures: {
-    type: Array,
-    default: () => []
-  },
-  productReviewCount: {
-    type: Number,
-    default: 0
-  },
-  productReviews: {
-    type: Array,
-    default: () => []
-  },
-  productSpecifications: {
-    type: Object,
-    default: () => ({})
-  },
-  productName: {
-    type: String,
-    default: ''
-  }
-});
-
-// Emits
-const emit = defineEmits(['add-to-cart', 'select-color', 'select-image']);
-
-// State
-const currentImageIndex = ref(0);
-const selectedColor = ref('');
-
-onMounted(() => {
-  if (props.productColors && props.productColors.length > 0) {
-    selectedColor.value = props.productColors[0].value;
-  }
-});
-
-// Methods
-const setCurrentImage = (index) => {
-  currentImageIndex.value = index;
-  emit('select-image', index);
-};
-
-const selectColor = (colorValue) => {
-  selectedColor.value = colorValue;
-  emit('select-color', colorValue);
-};
-
-const addToCart = () => {
-  if (props.productColors.length > 0 && !selectedColor.value) {
-    alert('Please select a color');
-    return;
-  }
-  
-  const colorName = props.productColors.find(c => c.value === selectedColor.value)?.label || selectedColor.value;
-  emit('add-to-cart', {
-    brandName: props.brandName,
-    modelName: props.modelName,
-    colorValue: selectedColor.value,
-    colorName: colorName
-  });
-};
-</script>
-<!-- layouts/headphones.vue -->
-<template>
-    <div class="container mx-auto px-4 py-8">
-      <!-- Main Product Section -->
-      <div class="grid grid-cols-1 md:grid-cols-2 gap-12">
-        <!-- Left: Product Images -->
-        <div class="product-images relative">
-          <!-- Main Image -->
-          <div class="main-image relative">
-            <img :src="mainImage" :alt="productName" class="w-full rounded-lg">
-            
-            <!-- Zoom Button -->
-            <button class="btn btn-circle btn-sm bg-base-100 absolute top-4 left-4">
-              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
-                <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
-              </svg>
-            </button>
-          </div>
-          
-          <!-- Thumbnail Gallery -->
-          <div class="thumbnails grid grid-cols-4 gap-2 mt-4">
-            <div
-v-for="(image, index) in productImages" :key="index" 
-                 class="thumbnail cursor-pointer border-2 rounded-md p-1" 
-                 :class="currentImageIndex === index ? 'border-primary' : 'border-transparent'">
-              <img
-:src="image" :alt="`${productName} - view ${index + 1}`" 
-                   class="w-full h-full object-cover" 
-                   @click="setCurrentImage(index)">
-            </div>
-          </div>
-        </div>
-        
-        <!-- Right: Product Details -->
-        <div class="product-details flex flex-col">
-          <!-- Brand and Model -->
-          <div class="mb-2">
-            <h1 class="text-3xl font-medium">
-              <span class="opacity-70">{{ brandName }}</span> 
-              <span class="font-bold">{{ modelName }}</span>
-            </h1>
-            <h2 class="text-2xl">{{ productSubtitle }}</h2>
-          </div>
-          
-          <!-- Color Selection -->
-          <div v-if="productColors.length > 0" class="my-8">
-            <div class="flex items-center justify-between mb-4">
-              <span class="font-medium">Select Color</span>
-            </div>
-            
-            <div class="flex gap-3 mb-6">
-              <button
-v-for="color in productColors" :key="color.value"
-                     class="w-10 h-10 rounded-full border-2" 
-                     :class="selectedColor === color.value ? 'border-primary' : 'border-transparent'"
-                     :style="`background-color: ${color.hex}`"
-                     @click="selectColor(color.value)"/>
-            </div>
-          </div>
-          
-          <!-- Add to Cart Section -->
-          <div class="flex items-center gap-4 my-8">
-            <button class="btn btn-primary flex-1 rounded-full" @click="addToCart">
-              <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 mr-2">
-                <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5V6a3.75 3.75 0 1 0-7.5 0v4.5m11.356-1.993 1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 0 1-1.12-1.243l1.264-12A1.125 1.125 0 0 1 5.513 7.5h12.974c.576 0 1.059.435 1.119 1.007ZM8.625 10.5a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm7.5 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
-              </svg>
-              ADD TO BAG
-            </button>
-            <div class="text-2xl font-bold">$ {{ productPrice }}</div>
-          </div>
-          
-          <!-- Product Information Accordion -->
-          <div class="product-info mt-6">
-            <div class="collapse collapse-arrow border-t">
-              <input type="checkbox" > 
-              <div class="collapse-title font-medium flex items-center">
-                Description
-                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-2 opacity-60">
-                  <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
-                </svg>
-              </div>
-              <div class="collapse-content"> 
-                <p>{{ productDescription }}</p>
-              </div>
-            </div>
-            
-            <div class="collapse collapse-arrow border-t">
-              <input type="checkbox" > 
-              <div class="collapse-title font-medium flex items-center">
-                Features
-                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-2 opacity-60">
-                  <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
-                </svg>
-              </div>
-              <div class="collapse-content"> 
-                <ul class="list-disc pl-5 space-y-1">
-                  <li v-for="(feature, index) in productFeatures" :key="index">
-                    {{ feature }}
-                  </li>
-                </ul>
-              </div>
-            </div>
-            
-            <div class="collapse collapse-arrow border-t">
-              <input type="checkbox" > 
-              <div class="collapse-title font-medium flex items-center">
-                Free Shipping & Returns
-                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-2 opacity-60">
-                  <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
-                </svg>
-              </div>
-              <div class="collapse-content"> 
-                <p>Free standard shipping on orders over $50. Free returns within 30 days of delivery.</p>
-              </div>
-            </div>
-            
-            <div v-if="productReviews.length > 0" class="collapse collapse-arrow border-t border-b">
-              <input type="checkbox" > 
-              <div class="collapse-title font-medium flex items-center">
-                Reviews ({{ productReviewCount }})
-                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 ml-2 opacity-60">
-                  <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
-                </svg>
-              </div>
-              <div class="collapse-content"> 
-                <div class="reviews space-y-4">
-                  <div v-for="(review, index) in productReviews" :key="index" class="review">
-                    <div class="font-medium">{{ review.name }}</div>
-                    <div class="text-sm opacity-60 mb-1">{{ review.date }}</div>
-                    <p>{{ review.comment }}</p>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-          
-          <!-- Technical Specifications -->
-          <div v-if="Object.keys(productSpecifications).length > 0" class="mt-8">
-            <h3 class="font-medium mb-4">Technical Specifications</h3>
-            <div class="grid grid-cols-2 gap-y-2 opacity-70">
-              <div v-for="(spec, key) in productSpecifications" :key="key" class="col-span-2 md:col-span-1">
-                <div class="grid grid-cols-2">
-                  <div class="font-medium">{{ key }}</div>
-                  <div>{{ spec }}</div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-      
-      <!-- Slot for additional content -->
-      <slot/>
-    </div>
-</template>
-  
\ No newline at end of file
diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts
index 9dc49e1..a374f74 100644
--- a/frontend/nuxt.config.ts
+++ b/frontend/nuxt.config.ts
@@ -1,5 +1,6 @@
 // https://nuxt.com/docs/api/configuration/nuxt-config
 export default defineNuxtConfig({
+  ssr: true,
   modules: ['@nuxt/eslint', '@nuxt/icon',
             '@nuxt/image'],
   css: ['~/assets/css/main.css',],  
diff --git a/frontend/pages/products/[category]/[id].vue b/frontend/pages/products/[category]/[id].vue
new file mode 100644
index 0000000..011dd73
--- /dev/null
+++ b/frontend/pages/products/[category]/[id].vue
@@ -0,0 +1,69 @@
+// frontend/pages/products/[category]/[id].vue
+<script setup>
+definePageMeta({
+  layout: 'product-detail-layout'
+});
+
+const route = useRoute();
+const category = route.params.category;
+
+// Category-specific content based on route parameter
+const categoryContent = computed(() => {
+  switch(category) {
+    case 'earphones':
+      return {
+        title: "About Earphones",
+        description: "Earphones deliver premium sound in a compact, portable design. Ideal for active lifestyles and on-the-go listening without compromising on audio quality.",
+        useCases: [
+          { title: "Active Lifestyles", description: "Stay motivated with your favorite tracks during workouts and runs." },
+          { title: "Commuters", description: "Enjoy a compact audio solution for your daily commute without bulk." },
+          { title: "Everyday Use", description: "Convenient for calls, podcasts, and music throughout your day." }
+        ]
+      };
+    case 'headphones':
+      return {
+        title: "About Headphones",
+        description: "Headphones provide an immersive listening experience with superior sound isolation and comfort for extended use.",
+        useCases: [
+          { title: "Audiophiles", description: "Experience your music with rich, detailed sound quality." },
+          { title: "Work Focus", description: "Block out distractions and create your perfect work environment." },
+          { title: "Home Entertainment", description: "Immerse yourself in movies, games, and music without disturbing others." }
+        ]
+      };
+    case 'speakers':
+      return {
+        title: "About Speakers",
+        description: "Our speakers fill your room with amazing sound, bringing your music, movies, and games to life with powerful, balanced audio.",
+        useCases: [
+          { title: "Home Audio", description: "Transform your living space with rich, room-filling sound." },
+          { title: "Parties & Gatherings", description: "Create the perfect atmosphere for social events." },
+          { title: "Smart Home", description: "Connect with voice assistants and smart home technology." }
+        ]
+      };
+    default:
+      return null;
+  }
+});
+
+const product = inject('product', ref(null));
+
+</script>
+
+<template>
+  <div v-if="product && categoryContent" class="mt-12 bg-gray-50 p-6 rounded-lg">
+    <h2 class="text-2xl font-bold mb-4">{{ categoryContent.title }}</h2>
+    <p class="mb-6 text-gray-700">{{ categoryContent.description }}</p>
+    
+    <div v-if="categoryContent.useCases" class="mt-8">
+      <h3 class="text-xl font-semibold mb-4">Perfect For</h3>
+      <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
+        <div
+v-for="(useCase, index) in categoryContent.useCases" :key="index" 
+            class="bg-white p-5 rounded-lg shadow-sm border border-gray-100">
+          <div class="font-medium text-lg mb-2">{{ useCase.title }}</div>
+          <p class="text-sm text-gray-600">{{ useCase.description }}</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
\ No newline at end of file
diff --git a/frontend/pages/products/[product].vue b/frontend/pages/products/[category]/index.vue
similarity index 94%
rename from frontend/pages/products/[product].vue
rename to frontend/pages/products/[category]/index.vue
index 1e01868..7133879 100644
--- a/frontend/pages/products/[product].vue
+++ b/frontend/pages/products/[category]/index.vue
@@ -5,7 +5,7 @@ definePageMeta({
 })
 
 const route = useRoute();
-const categorySlug = ref(route.params.product);
+const categorySlug = ref(route.params.category);
 
 const categoryNames = {
   'headphones': 'Headphones',
@@ -21,6 +21,7 @@ provide('pageTitle', pageTitle);
 
 // Use the composable to fetch products
 const apiUrl = useRuntimeConfig().public.apiUrl;
+
 const { data: products, error, pending } = useFetch(() => `${apiUrl}/api/products/${categorySlug.value}`, {
   method: 'GET',
   immediate: true,
@@ -39,7 +40,7 @@ const appliedFilters = inject('appliedFilters', computed(() => ({
 
 // Navigate to product detail
 const viewProductDetails = (productId) => {
-  navigateTo(`/products/${categorySlug.value}/${productId}`);
+  window.location.href = `/products/${categorySlug.value}/${productId}`
 };
 
 // Fallback data in case API fails (for development)
@@ -70,19 +71,17 @@ const mockProducts = [
   },
 ];
 
-// FIXED: Safe filter operations with proper checks
+
 const displayProducts = computed(() => {
-  // Start with either the API data or fallback to mockProducts
-  // Ensure we're always working with an array
   let result = Array.isArray(products.value) ? products.value : mockProducts;
   
   // Category filter - with safety check
   if (categorySlug.value) {
     result = result.filter(product => 
-      product?.category === categorySlug.value
+      product?.category === categorySlug.value || "DefaultType"
     );
   }
-  
+  console.log(result);
   // Apply search filter - with safety check
   if (appliedFilters.value.searchQuery) {
     const query = appliedFilters.value.searchQuery.toLowerCase();
@@ -189,7 +188,7 @@ const displayProducts = computed(() => {
         <h2 class="card-title text-lg">{{ product.name }}</h2>
         <p class="text-sm text-gray-600">{{ product.detail }}</p>
         <div class="flex justify-between items-center mt-2">
-          <div class="text-lg font-bold">${{ product.price.toFixed(2) }}</div>
+          <div class="text-lg font-bold">${{ product.price}}</div>
           <button class="btn btn-primary btn-sm" @click="viewProductDetails(product.id)">Buy Now</button>
         </div>
       </div>
diff --git a/frontend/pages/products/earphones/[id].vue b/frontend/pages/products/earphones/[id].vue
deleted file mode 100644
index 1bd29ed..0000000
--- a/frontend/pages/products/earphones/[id].vue
+++ /dev/null
@@ -1,10 +0,0 @@
-<script>
-    definePageMeta({
-        layout: 'product-page-layout'
-    })
-</script>
-<template>
-    <div>
-        hello
-    </div>
-</template>
\ No newline at end of file
diff --git a/frontend/pages/products/headphones/[id].vue b/frontend/pages/products/headphones/[id].vue
deleted file mode 100644
index 203f72e..0000000
--- a/frontend/pages/products/headphones/[id].vue
+++ /dev/null
@@ -1,10 +0,0 @@
-<script>
-    definePageMeta({
-        layout: 'product-page-layout'
-    })
-</script>
-<template>
-     <div>
-        hello
-    </div>
-</template>
\ No newline at end of file
diff --git a/frontend/pages/products/index.vue b/frontend/pages/products/index.vue
index 03ff209..533e5cc 100644
--- a/frontend/pages/products/index.vue
+++ b/frontend/pages/products/index.vue
@@ -30,7 +30,7 @@ const appliedFilters = inject('appliedFilters', computed(() => ({
 
 // Navigate to product detail
 const viewProductDetails = (productId) => {
-  navigateTo(`/products/detail/${productId}`);
+  window.location.href = `/products/${productId}`;
 };
 
 // Mock products for development or when API is unavailable
diff --git a/frontend/pages/products/speakers/[id].vue b/frontend/pages/products/speakers/[id].vue
deleted file mode 100644
index 0371837..0000000
--- a/frontend/pages/products/speakers/[id].vue
+++ /dev/null
@@ -1,11 +0,0 @@
-<script>
-    definePageMeta({
-        layout: 'product-page-layout'
-    })
-</script>
-
-<template>
-     <div>
-        hello
-    </div>
-</template>
\ No newline at end of file


Reviews:
user: login: premnacup
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-22T21:21:23Z

Review comments:
diff_hunk: @@ -18,9 +19,122 @@ def get_all_products(request):
     return Response(serializer.data)
 
 
-# Get product by name
 @api_view(["GET"])
-def get_product_by_name(request, name):
-    product = get_object_or_404(Product, name__iexact=name)
+def get_product_by_category(request, category):
+    print(category)
+    products = Product.objects.filter(category="DefaultType")
+    serializer = ProductSerializer(products, many=True)
+    return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_detailed(request, category, id):
+    product = get_object_or_404(Product, pk=id)
     serializer = ProductSerializer(product)
     return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_detailed_single_route(request, id):
+    product = get_object_or_404(Product, pk=id)
+    serializer = ProductSerializer(product)
+    return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_filters(request):
+    """
+    Get unique filter options for the product filters
+    Returns all unique brands, connection types, etc. from the products database
+    """
+    # Get unique brands and count of products for each
+    brands = (
+        Product.objects.values("brand").annotate(count=Count("brand")).order_by("brand")
+    )
+
+    # Get unique connection types and count of products for each
+    connections = (
+        Product.objects.values("connections")
+        .annotate(count=Count("connections"))
+        .order_by("connections")
+    )
+
+    # Get price ranges (calculated on the fly)
+    price_min = (
+        Product.objects.order_by("price").first().price
+        if Product.objects.exists()
+        else 0
+    )
+    price_max = (
+        Product.objects.order_by("-price").first().price
+        if Product.objects.exists()
+        else 1000
+    )
+
+    # Create price ranges based on actual data
+    price_ranges = []
+    if Product.objects.exists():
+        # Define price ranges
+        if price_min < 100:
+            price_ranges.append(
+                {
+                    "name": "Under $100",
+                    "min": 0,
+                    "max": 99.99,
+                    "count": Product.objects.filter(price__lt=100).count(),
+                }
+            )
+
+        if price_min < 300 and price_max > 100:
path: backend/server/controller/product_controller.py
user: login: premnacup
body: is this already correct or I misunderstood the condition?
created_at: 2025-03-22T21:04:26Z
author_association: MEMBER
diff_hunk: @@ -18,9 +19,122 @@ def get_all_products(request):
     return Response(serializer.data)
 
 
-# Get product by name
 @api_view(["GET"])
-def get_product_by_name(request, name):
-    product = get_object_or_404(Product, name__iexact=name)
+def get_product_by_category(request, category):
+    print(category)
+    products = Product.objects.filter(category="DefaultType")
+    serializer = ProductSerializer(products, many=True)
+    return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_detailed(request, category, id):
+    product = get_object_or_404(Product, pk=id)
     serializer = ProductSerializer(product)
     return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_detailed_single_route(request, id):
+    product = get_object_or_404(Product, pk=id)
+    serializer = ProductSerializer(product)
+    return Response(serializer.data)
+
+
+@api_view(["GET"])
+def get_product_filters(request):
+    """
+    Get unique filter options for the product filters
+    Returns all unique brands, connection types, etc. from the products database
+    """
+    # Get unique brands and count of products for each
+    brands = (
+        Product.objects.values("brand").annotate(count=Count("brand")).order_by("brand")
+    )
+
+    # Get unique connection types and count of products for each
+    connections = (
+        Product.objects.values("connections")
+        .annotate(count=Count("connections"))
+        .order_by("connections")
+    )
+
+    # Get price ranges (calculated on the fly)
+    price_min = (
+        Product.objects.order_by("price").first().price
+        if Product.objects.exists()
+        else 0
+    )
+    price_max = (
+        Product.objects.order_by("-price").first().price
+        if Product.objects.exists()
+        else 1000
+    )
+
+    # Create price ranges based on actual data
+    price_ranges = []
+    if Product.objects.exists():
+        # Define price ranges
+        if price_min < 100:
+            price_ranges.append(
+                {
+                    "name": "Under $100",
+                    "min": 0,
+                    "max": 99.99,
+                    "count": Product.objects.filter(price__lt=100).count(),
+                }
+            )
+
+        if price_min < 300 and price_max > 100:
path: backend/server/controller/product_controller.py
user: login: premnacup
body: Problem addressed by developer, all good.
created_at: 2025-03-22T21:20:41Z
author_association: MEMBER

PR comments:
user: login: KKhamwiset
created_at: 2025-03-22T20:55:19Z
body: the page itself isn't finish tho just routing and API
author_association: MEMBER
user: login: premnacup
created_at: 2025-03-22T20:56:07Z
body: Alright got it!
author_association: MEMBER