Skip to content

Commit

Permalink
Merge pull request #797 from IkkiOcean/agro-shop-frontend
Browse files Browse the repository at this point in the history
Added Complete Functionality to Search Bar
  • Loading branch information
manikumarreddyu authored Nov 5, 2024
2 parents 425e341 + 6c5539b commit 4cb453a
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 16 deletions.
31 changes: 31 additions & 0 deletions backend/controllers/shop/utilityController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const Product = require('../../model/shop/product')

const searchProducts = async (req, res) => {
const { query } = req.query;

if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}

try {
// Perform a case-insensitive, partial match search on the product's name and description
const products = await Product.find({
$or: [
{ name: { $regex: query, $options: 'i' } }, // 'i' for case-insensitive
{ description: { $regex: query, $options: 'i' } }
]
})
.populate('category reviews variants brand seller') // Populate related fields if needed
.limit(4) // Limit to top 4 results
.exec();

res.json(products);
} catch (error) {
console.error('Error fetching search results:', error);
res.status(500).json({ error: 'Internal server error' });
}
};

module.exports = {
searchProducts,
};
7 changes: 7 additions & 0 deletions backend/routes/shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const variantController = require('../controllers/shop/variantController');
const cartController = require('../controllers/shop/cartController')
const wishlistController = require('../controllers/shop/wishlistController');
const extendedUserController = require('../controllers/shop/profileController');
const UtilityController = require('../controllers/shop/utilityController');
/**
* Product Routes
*/
Expand Down Expand Up @@ -62,6 +63,7 @@ router.delete('/reviews/:id', reviewController.deleteReview);
/**
* Variant Routes
*/

router.get('/variants', variantController.getAllVariants);
router.get('/variants/:id', variantController.getVariantById);
router.post('/variants', variantController.createVariant);
Expand Down Expand Up @@ -96,5 +98,10 @@ router.get('/profile', extendedUserController.getAllExtendedUsers);// Get all ex
router.put('/profile/:id', extendedUserController.updateExtendedUser);// Update an existing extended user
router.delete('/profile/:id', extendedUserController.deleteExtendedUser);// Delete an extended user

/**
* Utility Routes
*/

router.get('/search', UtilityController.searchProducts);

module.exports = router;
21 changes: 5 additions & 16 deletions frontend/src/AgroShopAI/components/ShopNavbar.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import LOGO from "/favicon2.png";

import SearchBar from './sub-components/SearchBar';

function ShopNavbar() {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
Expand Down Expand Up @@ -54,7 +54,7 @@ function ShopNavbar() {

{/* Logo Section */}
<Link
to="/"
to="/agroshop"
className="flex items-center gap-2 group transition-all duration-300 ease-in-out transform hover:scale-105"
>
<img
Expand Down Expand Up @@ -87,14 +87,8 @@ function ShopNavbar() {
</div>

{/* Search Bar */}
<div className="flex-1 mx-4 hidden md:flex">
<input
type="text"
placeholder="Search for products..."
className="w-full px-3 py-2 rounded-l-md focus:outline-none text-black"
/>
<button className="bg-white text-green-600 px-4 rounded-r-md hover:bg-gray-200">Search</button>
</div>
<div className="hidden md:flex"></div>
<SearchBar />

{/* Right Corner Tabs */}
<div className="ml-auto flex items-center space-x-6">
Expand Down Expand Up @@ -146,12 +140,7 @@ function ShopNavbar() {
/>
)}
<div className="flex md:hidden mt-2 w-full">
<input
type="text"
placeholder="Search..."
className="w-full px-3 py-2 rounded-md text-black focus:outline-none"
/>
<button className="bg-white text-green-600 px-4 rounded-md ml-2 hover:bg-gray-200">Search</button>
<SearchBar/>
</div>
</nav>
);
Expand Down
122 changes: 122 additions & 0 deletions frontend/src/AgroShopAI/components/sub-components/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useState, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; // Import useNavigate

const SearchBar = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const dropdownRef = useRef(null);
const navigate = useNavigate(); // Initialize navigate
const debounceDelay = 300; // Delay in milliseconds
const timeoutRef = useRef(null); // Ref to store the timeout

// Close dropdown if clicking outside of it
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setIsDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

// Handle input change and search
const handleChange = (e) => {
setQuery(e.target.value);
setIsDropdownOpen(true);

// Clear previous timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

// Set a new timeout to trigger the search
timeoutRef.current = setTimeout(() => {
handleSearch(e.target.value);
}, debounceDelay);
};

const handleSearch = async (searchQuery) => {
if (!searchQuery) {
setResults([]);
setIsDropdownOpen(false);
return;
}

setLoading(true);

try {
const response = await fetch(`${import.meta.env.VITE_BACKEND_BASE_URL}api/search?query=${searchQuery}`);
const data = await response.json();
setResults(data);
} catch (error) {
console.error('Error fetching search results:', error);
setResults([]);
}
setLoading(false);
};

// Handle click on a search result
const handleResultClick = (productId) => {
// Redirect to the product details page using the product ID
navigate(`/agroshop/product/${productId}`);
setIsDropdownOpen(false); // Close dropdown after selection
};

return (
<div className="relative flex-1 mx-4 hidden md:flex">
{/* Search Input and Button */}
<input
type="text"
placeholder="Search..."
value={query}
onChange={handleChange}
className="w-full px-3 py-2 rounded-l-md focus:outline-none text-black"
/>
<button
onClick={() => handleSearch(query)} // Allow manual search
disabled={!query || loading}
className="bg-white text-green-600 px-4 rounded-r-md hover:bg-gray-200"
>
{loading ? 'Searching...' : 'Search'}
</button>

{/* Dropdown for Search Results */}
{isDropdownOpen && results.length > 0 && (
<div
ref={dropdownRef}
className="absolute top-full left-0 mt-2 w-full bg-white text-black rounded-lg shadow-lg transition-all duration-200"
>
{loading ? (
<p className="text-center text-gray-500 py-2">Loading...</p>
) : (
results.map((product) => (
<div
key={product._id}
className="flex items-center p-4 border-b border-gray-200 rounded-lg hover:bg-gray-100 cursor-pointer"
onClick={() => handleResultClick(product._id)} // Handle click
>
{/* Display the first image */}
{product.images && product.images[0] && (
<img
src={product.images[0]}
alt={product.name}
className="w-16 h-16 rounded-md mr-4 object-cover"
/>
)}
<div className="flex flex-col">
<h3 className="text-lg font-semibold">{product.name}</h3>
<span className="text-gray-600 text-sm">{product.brand?.name || 'Unknown Brand'}</span>
</div>
</div>
))
)}
</div>
)}
</div>
);
};

export default SearchBar;

0 comments on commit 4cb453a

Please sign in to comment.