Skip to content

Commit 46dce93

Browse files
committed
Added Search functionality
1 parent 0b26d9e commit 46dce93

File tree

11 files changed

+168
-35
lines changed

11 files changed

+168
-35
lines changed

backend/.env

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ADMIN_MAIL_ID=[email protected]
2+
ADMIN_MAIL_PASS=hsabloewopnpzocd
3+
API_KEY=223911979549715
4+
API_SECRET=VhE_AOzw2M1hNGsJjkHbpVNFpik
5+
BASE_URL="http://localhost:3000"
6+
CLOUD_NAME=djtdg1hcq
7+
DATABASE_URL=mongodb+srv://iamakr97:[email protected]/?retryWrites=true&w=majority&appName=Cluster0
8+
JWT_SECRET=amitkumar
9+
PORT=4000
10+
PROFILE_URL=https://ui-avatars.com/api/?name=
11+
RAZORPAY_KEY_ID=rzp_test_r8Eveaq11tlbJr
12+
RAZORPAY_KEY_SECRET=GBj7MrDUwPiiq4gk1jFqL86Z

backend/Utils/fileUpload.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const cloudinary = require('cloudinary').v2;
33
exports.isFileTypeSupported = (type, supportedTypes) => {
44
return supportedTypes.includes(type);
55
}
6-
6+
77
exports.uploadFileToCloudinary = async (file, folder, quality) => {
88
try {
99
const options = {};

backend/controllers/auth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const jwt = require('jsonwebtoken');
77
require('dotenv').config();
88
const { mailSender } = require('../Utils/mailSender');
99
const emailTemplate = require('../template/OTP_mail');
10-
10+
1111

1212
exports.sendOTP = async (req, res) => {
1313
try {

backend/controllers/search.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const Product = require('../models/productSchema');
2+
3+
exports.searchProduct = async (req, res) => {
4+
try {
5+
const { query } = req.query;
6+
if (!query) {
7+
return res.status(400).json({
8+
status: false,
9+
message: 'Query is required'
10+
});
11+
}
12+
const results = await Product.find({
13+
$or: [
14+
{ title: { $regex: query, $options: 'i' } },
15+
{ description: { $regex: query, $options: 'i' } },
16+
{ category: { $regex: query, $options: 'i' } },
17+
{ brand: { $regex: query, $options: 'i' } }
18+
]
19+
});
20+
21+
res.status(200).json({
22+
success: true,
23+
message: "Search Product find successfully",
24+
results
25+
});
26+
27+
} catch (error) {
28+
res.status(500).json({
29+
status: false,
30+
message: "Internal Server Error",
31+
error: error.message
32+
});
33+
}
34+
}

backend/routes/routes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const { addProduct, allProducts, featuredProducts, fetchProductDetails, getAllPr
1616
const { allCartItems, addToCart, removeFromCart } = require('../controllers/cartHandlers');
1717
const { checkout, paymentVerification } = require('../controllers/paymentsHandler');
1818
const { myOrders, allOrders, updateOrder } = require('../controllers/ordersHandler');
19+
const { searchProduct } = require('../controllers/search');
1920

2021
router.post('/sendotp', sendOTP);
2122
router.post('/signup', signup);
@@ -48,4 +49,8 @@ router.get('/myOrders', auth, isUser, myOrders);
4849
router.get('/allOrders', auth, isAdmin, allOrders);
4950
router.post('/updateOrder/:orderId', auth, isAdmin, updateOrder);
5051

52+
53+
//search
54+
router.get('/search', searchProduct);
55+
5156
module.exports = router;

frontend/package-lock.json

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"dotenv": "^16.4.5",
1212
"js-cookie": "^3.0.5",
1313
"jwt-decode": "^4.0.0",
14+
"lodash.debounce": "^4.0.8",
1415
"react": "^18.2.0",
1516
"react-dom": "^18.2.0",
1617
"react-hot-toast": "^2.4.1",

frontend/src/App.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ function App() {
2929
if (token) {
3030
const decodedToken = jwtDecode(token);
3131
const currentTime = Date.now() / 1000;
32-
32+
3333
if (decodedToken.exp < currentTime) {
3434
localStorage.removeItem('token');
3535
} else {
@@ -60,7 +60,7 @@ function App() {
6060
<Route path='/user/address' element={<Address />} />
6161
<Route path='/user/addAddress' element={<AddAddress />} />
6262
<Route path='/user/payment' element={<PaymentPage />} />
63-
<Route path='/admin/dashboard' element={<AdminDashboard/>} />
63+
<Route path='/admin/dashboard' element={<AdminDashboard />} />
6464
</Routes>
6565
<Footer />
6666
</>

frontend/src/Components/Navbar.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,39 @@
5959
color: var(--blue-color);
6060
}
6161

62+
.search-product {
63+
background-color: white;
64+
width: 32rem;
65+
margin: 0 auto;
66+
position: absolute;
67+
z-index: 1000;
68+
left: 39%;
69+
}
70+
71+
.search-item {
72+
display: flex;
73+
align-items: center;
74+
gap: 10px;
75+
padding: 10px;
76+
border-bottom: 1px solid #ddd;
77+
cursor: pointer;
78+
}
79+
80+
.search-item:last-child {
81+
border-bottom: none;
82+
}
83+
84+
#product-image {
85+
width: 50px;
86+
height: 50px;
87+
object-fit: cover;
88+
}
89+
90+
.search-item p {
91+
margin: 0;
92+
font-size: 14px;
93+
}
94+
6295
@media (max-width: 1100px) {
6396
.nav-container {
6497
max-width: 90%;

frontend/src/Components/Navbar.js

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,89 @@
1-
import React, { useState } from 'react'
1+
import React, { useState, useCallback } from 'react';
22
import './Navbar.css';
3-
import { Link } from 'react-router-dom';
3+
import { Link, useNavigate } from 'react-router-dom';
44
import logo from '../Assets/logo.png';
55
import { CiSearch } from "react-icons/ci";
66
import { PiShoppingCart } from "react-icons/pi";
77
import { FaRegUserCircle } from "react-icons/fa";
8+
import axios from 'axios';
9+
import debounce from 'lodash.debounce';
810

911
function Navbar() {
12+
const navigate = useNavigate();
1013
const [searchBox, setSearchBox] = useState('');
11-
function searchHandler(e) {
12-
e.preventDefault();
13-
setSearchBox("");
14-
// console.log(searchBox);
14+
const [searchResults, setSearchResults] = useState([]);
15+
16+
// Function to make the API call
17+
const searchProducts = async (query) => {
18+
if (query === '') {
19+
setSearchResults([]);
20+
return;
21+
}
22+
23+
try {
24+
const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/search?query=${query}`);
25+
setSearchResults(response.data.results);
26+
console.log(response.data.results)
27+
} catch (error) {
28+
console.error('Error fetching search results:', error);
29+
}
30+
};
31+
32+
const debouncedSearch = useCallback(
33+
debounce((query) => searchProducts(query), 500),
34+
[]
35+
);
36+
37+
const searchHandler = (e) => {
38+
const value = e.target.value;
39+
console.log(value);
40+
setSearchBox(value);
41+
debouncedSearch(value);
42+
};
43+
function productCardClickHandler(id) {
44+
setSearchBox('');
45+
setSearchResults([]);
46+
navigate(`/product/${id}`);
1547
}
48+
1649
return (
17-
<div className='nav-bar'>
18-
<div className="nav-container">
19-
<div className="left-nav">
20-
<Link to='/' > <img src={logo} alt="Urban Sole" className='logo' /> </Link>
21-
</div>
22-
<div className='search-box'>
23-
<form onSubmit={searchHandler}>
24-
<input
25-
className="search-input"
26-
type="text"
27-
placeholder='Search for Products'
28-
value={searchBox}
29-
onChange={(e) => setSearchBox(e.target.value)}
30-
/>
31-
<button className='search-btn'><CiSearch className='search-icon' /></button>
32-
</form>
33-
</div>
34-
<div className="right-nav">
35-
<Link to='/myAccount'><FaRegUserCircle className='nav-icons' /></Link>
36-
<Link to='/cart'><PiShoppingCart className='nav-icons' /></Link>
50+
<>
51+
<div className='nav-bar'>
52+
<div className="nav-container">
53+
<div className="left-nav">
54+
<Link to='/' > <img src={logo} alt="Urban Sole" className='logo' /> </Link>
55+
</div>
56+
<div className='search-box'>
57+
<form onSubmit={(e) => e.preventDefault()}>
58+
<input
59+
className="search-input"
60+
type="text"
61+
placeholder='Search for Products'
62+
value={searchBox}
63+
onChange={searchHandler}
64+
/>
65+
<button className='search-btn'><CiSearch className='search-icon' /></button>
66+
</form>
67+
</div>
68+
<div className="right-nav">
69+
<Link to='/myAccount'><FaRegUserCircle className='nav-icons' /></Link>
70+
<Link to='/cart'><PiShoppingCart className='nav-icons' /></Link>
71+
</div>
3772
</div>
3873
</div>
39-
</div>
40-
)
74+
75+
{searchResults.length > 0 && (
76+
<div className='search-product' >
77+
{searchResults.map((product) => (
78+
<div key={product._id} className='search-item' onClick={()=>productCardClickHandler(product._id)}>
79+
<img src={product.image[0]} alt={product.title} id='product-image' />
80+
<p>{product.title}</p>
81+
</div>
82+
))}
83+
</div>
84+
)}
85+
</>
86+
);
4187
}
4288

43-
export default Navbar;
89+
export default Navbar;

0 commit comments

Comments
 (0)