added show more functionality && added calendar for tour requests

This commit is contained in:
Juthatip McDevitt 2024-03-16 00:02:31 -05:00
parent 4b0a86b0dc
commit af0fb622b8
8 changed files with 176 additions and 20 deletions

View file

@ -11,6 +11,7 @@
"@reduxjs/toolkit": "^2.2.1",
"firebase": "^10.8.1",
"react": "^18.2.0",
"react-calendar": "^4.8.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-redux": "^9.1.0",
@ -1639,6 +1640,19 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
"integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA=="
},
"node_modules/@types/lodash.memoize": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz",
"integrity": "sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": {
"version": "20.11.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz",
@ -1702,6 +1716,14 @@
"vite": "^4 || ^5"
}
},
"node_modules/@wojtekmaj/date-utils": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz",
"integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==",
"funding": {
"url": "https://github.com/wojtekmaj/date-utils?sponsor=1"
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@ -2215,6 +2237,14 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/clsx": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -3081,6 +3111,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-user-locale": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.1.tgz",
"integrity": "sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==",
"dependencies": {
"@types/lodash.memoize": "^4.1.7",
"lodash.memoize": "^4.1.1"
},
"funding": {
"url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -3835,6 +3877,11 @@
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -3978,7 +4025,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -4430,7 +4476,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -4500,6 +4545,31 @@
"node": ">=0.10.0"
}
},
"node_modules/react-calendar": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-4.8.0.tgz",
"integrity": "sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA==",
"dependencies": {
"@wojtekmaj/date-utils": "^1.1.3",
"clsx": "^2.0.0",
"get-user-locale": "^2.2.1",
"prop-types": "^15.6.0",
"warning": "^4.0.0"
},
"funding": {
"url": "https://github.com/wojtekmaj/react-calendar?sponsor=1"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@ -4523,8 +4593,7 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-redux": {
"version": "9.1.0",
@ -5561,6 +5630,14 @@
}
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/websocket-driver": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",

View file

@ -13,6 +13,7 @@
"@reduxjs/toolkit": "^2.2.1",
"firebase": "^10.8.1",
"react": "^18.2.0",
"react-calendar": "^4.8.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1",
"react-redux": "^9.1.0",

View file

@ -0,0 +1,34 @@
import { useEffect, useState } from "react"
const Contact = ({listing}) => {
const [agent, setAgent] = useState(null);
useEffect(() => {
const fetchAgent = async () => {
try {
const res = await fetch(`/server/user/${listing.userRef}`);
const data = await res.json();
setAgent(data);
} catch (error) {
console.log(error);
}
};
fetchAgent();
}, [listing.userRef]);
return (
<div>
{
agent && (
<div className="">
<p>Contact<span>{agent.username}</span>{' '} for {' '} <span>{listing.name.toLowerCase()}</span></p>
</div>
)
}
</div>
)
}
export default Contact

View file

@ -4,7 +4,7 @@ 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'/>
<img src={listing.imageUrls[0]} alt="" className='h-[150px] 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')}

View file

@ -1,11 +1,12 @@
//import { IoIosSearch } from "react-icons/io";
import { IoIosLock } from "react-icons/io";
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux'
const Navbar = () => {
const {currentUser} = useSelector((state) => state.user)
return (
<header className="bg-blue-950">
<div className="flex justify-between items-center mx-auto">
@ -15,14 +16,10 @@ const Navbar = () => {
<h3 className=" text-gray-600 text-xs mt-1 uppercase tracking-widest">Lifestyle properties</h3>
</div>
</Link>
{/*<form className="bg-white rounded-lg flex items-center">
<input type="text" placeholder="Search your dream property" className="bg-transparent px-4 py-2 focus:outline-none text-sm w-60 sm:w-64"/>
<IoIosSearch className="text-slate-600 text-lg"/>
</form>*/}
<ul className="flex gap-4 sm:px-10 px-5">
<Link to="/"><li className="text-white tracking-wide text-center uppercase text-sm hidden md:inline">Home</li></Link>
<Link to="/"><li className="text-white tracking-wide text-center uppercase text-sm hidden md:inline">Buying</li></Link>
<Link to="/"><li className="text-white tracking-wide text-center uppercase text-sm hidden md:inline">Selling</li></Link>
<Link to="/"><li className="text-white tracking-wide text-center uppercase text-sm hidden md:inline">Renting</li></Link>
<Link to="/contact"><li className="text-white tracking-wide text-center uppercase text-sm hidden md:inline">Contact</li></Link>
<Link to="/profile" className="flex items-center gap-1">
{currentUser ? (

View file

@ -20,8 +20,8 @@ const Search = () => {
}
}, [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"/>
<form onSubmit={handleSubmit} className="text-black mt-5 bg-white flex items-center border border-blue-800 rounded-md">
<input type="text" placeholder="Search your dream property" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="px-4 py-2 focus:outline-none bg-transparent text-sm w-60 sm:w-80"/>
<button>
<IoIosSearch className="text-slate-600 text-lg m-2"/>
</button>

View file

@ -8,8 +8,15 @@ import 'swiper/css/thumbs';
import { FreeMode, Navigation, Thumbs } from 'swiper/modules';
import { BiSolidMap } from "react-icons/bi";
import Calendar from "react-calendar";
import "react-calendar/dist/Calendar.css";
const Listing = () => {
const [date, changeDate] = useState(new Date());
function changeValue(val) {
changeDate(val);
}
const [thumbsSwiper, setThumbsSwiper] = useState(null);
const params = useParams();
const [loading, setLoading] = useState(false);
@ -45,9 +52,10 @@ const Listing = () => {
{error && <p className="text-center my-10 text-xl text-red-700">Something went wrong</p>}
{listing && !loading && !error &&
<>
<Swiper style={{'--swiper-navigation-color': 'transparent', '--swiper-pagination-color': 'transparent',}} loop={true} spaceBetween={10} navigation={true} thumbs={{ swiper: thumbsSwiper }} modules={[FreeMode, Navigation, Thumbs]} className="mySwiper2" >
{listing.imageUrls.map(url =>
<SwiperSlide key={url}><img src={url} alt="" className="rounded-md my-1"/></SwiperSlide>
<SwiperSlide key={url}><img src={url} alt="" className="rounded-sm md:my-1"/></SwiperSlide>
)}
</Swiper>
<Swiper onSwiper={setThumbsSwiper} loop={true} spaceBetween={10} slidesPerView={5} freeMode={true} watchSlidesProgress={true} modules={[FreeMode, Navigation, Thumbs]} className="mySwiper">
@ -55,7 +63,8 @@ const Listing = () => {
<SwiperSlide key={url}><img src={url} alt="" /></SwiperSlide>
)}
</Swiper>
<div className="flex flex-col max-w-4xl mx-auto p-4 gap-4">
<div className="flex max-w-4xl mx-auto flex-col md:flex-row">
<div className="flex flex-col p-4 gap-4 max-w-4xl md:max-w-2xl">
<p className="text-2xl font-semibold mt-5 capitalize">
{listing.name} - $ {''}
{listing.offer ? listing.discountPrice.toLocaleString('en-US') : listing.currentPrice.toLocaleString('en-US')}
@ -90,7 +99,19 @@ const Listing = () => {
<span className='font-semibold text-black mt-4 mb-2 text-xl capitalize'>About this property</span>
<span>{listing.description}</span>
</p>
<button onClick={() => window.location = 'mailto:chicagoland@example.com'} className='bg-blue-950 text-white rounded-md uppercase hover:opacity-95 p-2 mb-10 text-sm'>Contact Agent</button>
<button onClick={() => window.location = 'mailto:chicagoland@example.com'} className='bg-blue-950 text-white rounded-md uppercase hover:opacity-95 p-2 mb-10 text-sm font-semibold'>Contact Agent</button>
</div>
<div className="flex flex-col p-2 gap-2 mt-3 max-w-4xl md:max-w-2xl">
<div className="border border-gray-300 p-4 rounded-md">
<h2 className="text-xl text-blue-950 font-semibold capitalize md:text-start text-center">Schedule tour</h2>
<h3 className="md:text-start text-center">What is your preferred tour date?</h3>
<div className="mt-2 flex flex-col justify-center items-center">
<Calendar onChange = {changeValue} value = {date} />
<p className="mt-2 mb-6 text-center text-blue-700">The selected date is - {date.toLocaleDateString()}</p>
</div>
<button className="w-full bg-gray-300 rounded-md uppercase hover:opacity-95 p-2 text-sm font-semibold">Request tour</button>
</div>
</div>
</div>
</>
}

View file

@ -6,6 +6,7 @@ import ListingCard from "../components/ListingCard";
const SearchProperties = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [showMore, setShowMore] = useState(false);
const [listings, setListings] = useState([]);
const [sidebarSearch, setSidebarSearch] = useState({
searchTerm: '',
@ -38,9 +39,15 @@ const SearchProperties = () => {
}
const fetchListings = async () => {
setLoading(true);
setShowMore(false)
const searchQuery = urlParams.toString();
const res = await fetch(`/server/listing/get?${searchQuery}`);
const data = await res.json();
if(data.length > 11){
showMore(true);
} else{
setShowMore(false);
}
setListings(data);
setLoading(false);
};
@ -81,10 +88,25 @@ const SearchProperties = () => {
navigate(`/search?${searchQuery}`);
};
//show more functionality
const onShowMore = async () => {
const numberOfListings = listings.length;
const startIndex = numberOfListings;
const urlParams = new URLSearchParams(location.search);
urlParams.set('startIndex', startIndex);
const searchQuery = urlParams.toString();
const res = await fetch(`/server/listing/get?${searchQuery}`);
const data = await res.json();
if (data.length < 12) {
setShowMore(false);
}
setListings([...listings, ...data]);
}
return (
<div className="flex flex-col md:flex-row">
<div className="p-10 border-b-2 md:border-r-2 md:min-h-screen">
<div className="flex flex-col lg:flex-row">
<div className="p-8 border-b-2 md:border-r-2 lg: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>
@ -135,8 +157,8 @@ const SearchProperties = () => {
</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">
<h1 className="text-2xl font-semibold border-b p-8 text-blue-900 capitalize">Listing results</h1>
<div className="flex flex-wrap p-8 gap-4">
{!loading && listings.length === 0 &&(
<p className="text-red-700 text-md font-semibold tracking-wider capitalize ">No listing found</p>
)}
@ -144,6 +166,10 @@ const SearchProperties = () => {
<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}/>))}
{showMore &&(
<button onClick={onShowMore} className="w-full text-center text-blue-900 hover:underline p-10">Show more</button>
)}
</div>
</div>
</div>