completed search funtionality

This commit is contained in:
Juthatip McDevitt 2024-03-15 20:19:28 -05:00
parent 917b01c521
commit 4b0a86b0dc
5 changed files with 215 additions and 5 deletions

View file

@ -9,6 +9,7 @@ import PrivateRoute from './components/PrivateRoute'
import CreatListing from './pages/CreatListing' import CreatListing from './pages/CreatListing'
import UpdateListing from './pages/UpdateListing' import UpdateListing from './pages/UpdateListing'
import Listing from './pages/Listing' import Listing from './pages/Listing'
import SearchProperties from './pages/SearchProperties'
const App = () => { const App = () => {
@ -23,6 +24,7 @@ const App = () => {
<Route path='/updateListing/:listingId' element={<UpdateListing/>}/> <Route path='/updateListing/:listingId' element={<UpdateListing/>}/>
</Route> </Route>
<Route path='/listing/:listingId' element={<Listing/>}/> <Route path='/listing/:listingId' element={<Listing/>}/>
<Route path='/search' element={<SearchProperties/>}/>
<Route path='/login' element={<Login/>}/> <Route path='/login' element={<Login/>}/>
<Route path='/signup' element={<SignUp/>}/> <Route path='/signup' element={<SignUp/>}/>
<Route path='/contact' element={<Contact/>}/> <Route path='/contact' element={<Contact/>}/>

View file

@ -0,0 +1,26 @@
import {Link} from 'react-router-dom'
const ListingCard = ({listing}) => {
return (
<div className='bg-white shadow-md hover:shadow-lg transition-shadow overflow-hidden rounded-md w-full sm:w-[230px]'>
<Link to={`/listing/${listing._id}`}>
<img src={listing.imageUrls[0]} alt="" className='h-[300px] sm:h-[150px] w-full object-cover hover:scale-105 transition-scale duration-300'/>
<div className="p-2">
<p className='font-semibold text-gray-800 text-xl truncate mt-2 mb-1'>
${listing.offer ? listing.discountPrice.toLocaleString('en-Us') : listing.currentPrice.toLocaleString('en-Us')}
{listing.type === 'rent' && '/month'}
</p>
<div className="flex mb-1 gap-2">
<p className='text-sm'><span className='font-bold'>{listing.bed}</span> bds |</p>
<p className='text-sm'><span className='font-bold'>{listing.bath}</span> ba </p>
</div>
<div className='mb-4'>
<p className="text-sm truncate">{listing.address}</p>
</div>
</div>
</Link>
</div>
)
}
export default ListingCard

View file

@ -0,0 +1,32 @@
import { IoIosSearch } from "react-icons/io";
import { useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
const Search = () => {
const navigate = useNavigate();
const [searchTerm, setSearchTerm] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('searchTerm', searchTerm);
const searchQuery = urlParams.toString();
navigate(`/search?${searchQuery}`);
};
useEffect(() => {
const urlParams = new URLSearchParams(location.search);
const searchTermFormUrl = urlParams.get('searchTerm');
if(searchTermFormUrl){
setSearchTerm(searchTermFormUrl);
}
}, [location.search])
return (
<form onSubmit={handleSubmit} className="text-black mt-5 bg-white rounded-lg flex items-center">
<input type="text" placeholder="Search your dream property" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="px-4 py-3 focus:outline-none bg-transparent text-sm w-60 sm:w-80"/>
<button>
<IoIosSearch className="text-slate-600 text-lg m-2"/>
</button>
</form>
)
}
export default Search

View file

@ -1,4 +1,4 @@
import { IoIosSearch } from "react-icons/io"; import Search from "../components/Search"
const Home = () => { const Home = () => {
return ( return (
@ -8,10 +8,7 @@ const Home = () => {
<div className="relative z-30 text-white text-center flex flex-col justify-center items-center"> <div className="relative z-30 text-white text-center flex flex-col justify-center items-center">
<h1 className="uppercase tracking-tight text-5xl font-serif mb-4 text-shadow-lg shadow-black">Chicago properties for sale</h1> <h1 className="uppercase tracking-tight text-5xl font-serif mb-4 text-shadow-lg shadow-black">Chicago properties for sale</h1>
<p className="text-shadow-DEFAULT shadow-black">Lorem ipsum dolor sit, amet consectetur adipisicing elit. <br />Alias dolorem error atque aut, asperiores et tempore iste quaerat pariatur maxime?</p> <p className="text-shadow-DEFAULT shadow-black">Lorem ipsum dolor sit, amet consectetur adipisicing elit. <br />Alias dolorem error atque aut, asperiores et tempore iste quaerat pariatur maxime?</p>
<form className="text-black mt-5 bg-white rounded-lg flex items-center"> <Search/>
<input type="text" placeholder="Search your dream property" className="px-4 py-3 focus:outline-none bg-transparent text-sm w-60 sm:w-80"/>
<IoIosSearch className="text-slate-600 text-lg m-2"/>
</form>
</div> </div>
</div> </div>

View file

@ -0,0 +1,153 @@
import { useEffect, useState } from "react"
import { useNavigate } from "react-router-dom";
import ListingCard from "../components/ListingCard";
const SearchProperties = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [listings, setListings] = useState([]);
const [sidebarSearch, setSidebarSearch] = useState({
searchTerm: '',
type: 'all',
parking: false,
offer: false,
furnished: false,
sort: 'created_at',
order: 'desc',
});
useEffect(() => {
const urlParams = new URLSearchParams(location.search);
const searchTermFromUrl = urlParams.get('searchTerm');
const typeFromUrl = urlParams.get('type');
const parkingFromUrl = urlParams.get('parking');
const furnishedFromUrl = urlParams.get('furnished');
const offerFromUrl = urlParams.get('offer');
const sortFromUrl = urlParams.get('sort');
const orderFromUrl = urlParams.get('order');
if(searchTermFromUrl || typeFromUrl || parkingFromUrl || furnishedFromUrl || offerFromUrl || sortFromUrl || orderFromUrl){
setSidebarSearch({
searchTerm: searchTermFromUrl || '',
type: typeFromUrl || 'all',
parking: parkingFromUrl === 'true' ? true : false,
furnished: furnishedFromUrl === 'true' ? true : false,
offer: offerFromUrl === 'true' ? true : false,
sort: sortFromUrl || 'created_at',
order: orderFromUrl || 'desc',
});
}
const fetchListings = async () => {
setLoading(true);
const searchQuery = urlParams.toString();
const res = await fetch(`/server/listing/get?${searchQuery}`);
const data = await res.json();
setListings(data);
setLoading(false);
};
fetchListings();
}, [location.search])
//Search by...
const handleChange = (e) => {
if(e.target.id === 'all' || e.target.id === 'rent' || e.target.id === 'sale'){
setSidebarSearch({...sidebarSearch, type: e.target.id});
}
if(e.target.id === 'searchTerm'){
setSidebarSearch({...sidebarSearch, searchTerm: e.target.value});
}
if(e.target.id === 'parking' || e.target.id === 'furnished' || e.target.id === 'offer'){
setSidebarSearch({...sidebarSearch, [e.target.id]: e.target.checked || e.target.checked === 'true' ? true : false,});
}
if(e.target.id === 'sort_order') {
const sort = e.target.value.split('_')[0] || 'created_at';
const order = e.target.value.split('_')[1] || 'desc';
setSidebarSearch({ ...sidebarSearch, sort, order });
}
};
//Submit form
const handleSubmit = (e) => {
e.preventDefault();
const urlParams = new URLSearchParams();
urlParams.set('searchTerm', sidebarSearch.searchTerm);
urlParams.set('type', sidebarSearch.type);
urlParams.set('parking', sidebarSearch.parking);
urlParams.set('furnished', sidebarSearch.furnished);
urlParams.set('offer', sidebarSearch.offer);
urlParams.set('sort', sidebarSearch.sort);
urlParams.set('order', sidebarSearch.order);
const searchQuery = urlParams.toString();
navigate(`/search?${searchQuery}`);
};
return (
<div className="flex flex-col md:flex-row">
<div className="p-10 border-b-2 md:border-r-2 md:min-h-screen">
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
<div className="flex items-center gap-2">
<label className="whitespace-nowrap font-semibold text-blue-900">Search: </label>
<input type="text" id="searchTerm" placeholder="Search your dream property" value={sidebarSearch.searchTerm} onChange={handleChange} className="border rounded-md p-2 w-full text-sm"/>
</div>
<div className="flex gap-2 flex-wrap items-center">
<label className="whitespace-nowrap font-semibold text-blue-900">Type: </label>
<div className="flex gap-2">
<input type='checkbox' onChange={handleChange} checked={sidebarSearch.type === 'all'} id="all" className="w-4"/>
<span className="text-sm">Rent & Sale</span>
</div>
<div className="flex gap-2">
<input type='checkbox' onChange={handleChange} checked={sidebarSearch.type === 'rent'} id="rent" className="w-4"/>
<span className="text-sm">Rent</span>
</div>
<div className="flex gap-2">
<input type='checkbox' onChange={handleChange} checked={sidebarSearch.type === 'sale'} id="sale" className="w-4"/>
<span className="text-sm">Sale</span>
</div>
<div className="flex gap-2">
<input type='checkbox' onChange={handleChange} checked={sidebarSearch.offer} id="offer" className="w-4"/>
<span className="text-sm">Offer</span>
</div>
</div>
<div className="flex gap-2 flex-wrap items-center">
<label className="whitespace-nowrap font-semibold text-blue-900">Amenities: </label>
<div className="flex gap-2">
<input type='checkbox' onChange={handleChange} checked={sidebarSearch.parking} id="parking" className="w-4"/>
<span className="text-sm">Garage</span>
</div>
<div className="flex gap-2">
<input type='checkbox' onChange={handleChange} checked={sidebarSearch.furnished} id="furnished" className="w-4"/>
<span className="text-sm">Furnished</span>
</div>
</div>
<div className="flex items-center gap-2">
<label className="whitespace-nowrap font-semibold text-blue-900">Sort: </label>
<select onChange={handleChange} defaultValue={'created_at_desc'} id="sort_order" className="border rounded-md p-1 text-sm">
<option value='currentPrice_desc'>Price hight to low</option>
<option value='currentPrice_asc'>Price low to hight</option>
<option value='createdAt_desc'>Latest</option>
<option value='createdAt_asc'>Oldest</option>
</select>
</div>
<button className="bg-blue-950 text-white font-semibold p-1 rounded-md hover:opacity-95">Search</button>
</form>
</div>
<div className="flex-1">
<h1 className="text-2xl font-semibold border-b p-10 text-blue-900 capitalize">Listing results</h1>
<div className="flex flex-wrap p-10 gap-4">
{!loading && listings.length === 0 &&(
<p className="text-red-700 text-md font-semibold tracking-wider capitalize ">No listing found</p>
)}
{loading && (
<p className="text-blue-900 text-md font-semibold tracking-wider capitalize text-center w-full">loading...</p>
)}
{!loading && listings && listings.map((listing) => (<ListingCard key={listing._id} listing={listing}/>))}
</div>
</div>
</div>
)
}
export default SearchProperties