created and completed listing page functionality
This commit is contained in:
parent
0b792b751c
commit
cd79548dfc
5 changed files with 170 additions and 2 deletions
21
real_estate/client/package-lock.json
generated
21
real_estate/client/package-lock.json
generated
|
@ -15,7 +15,8 @@
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-redux": "^9.1.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-router-dom": "^6.22.3",
|
"react-router-dom": "^6.22.3",
|
||||||
"redux-persist": "^6.0.0"
|
"redux-persist": "^6.0.0",
|
||||||
|
"swiper": "^11.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.56",
|
"@types/react": "^18.2.56",
|
||||||
|
@ -5202,6 +5203,24 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/swiper": {
|
||||||
|
"version": "11.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.0.7.tgz",
|
||||||
|
"integrity": "sha512-cDfglW1B6uSmB6eB6pNmzDTNLmZtu5bWWa1vak0RU7fOI9qHjMzl7gVBvYSl34b0RU2N11HxxETJqQ5LeqI1cA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/swiperjs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "open_collective",
|
||||||
|
"url": "http://opencollective.com/swiper"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-redux": "^9.1.0",
|
"react-redux": "^9.1.0",
|
||||||
"react-router-dom": "^6.22.3",
|
"react-router-dom": "^6.22.3",
|
||||||
"redux-persist": "^6.0.0"
|
"redux-persist": "^6.0.0",
|
||||||
|
"swiper": "^11.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.56",
|
"@types/react": "^18.2.56",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Navbar from './components/Navbar'
|
||||||
import PrivateRoute from './components/PrivateRoute'
|
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'
|
||||||
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
@ -21,6 +22,7 @@ const App = () => {
|
||||||
<Route path='/createListing' element={<CreatListing/>}/>
|
<Route path='/createListing' element={<CreatListing/>}/>
|
||||||
<Route path='/updateListing/:listingId' element={<UpdateListing/>}/>
|
<Route path='/updateListing/:listingId' element={<UpdateListing/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path='/listing/:listingId' element={<Listing/>}/>
|
||||||
<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/>}/>
|
||||||
|
|
|
@ -3,3 +3,46 @@
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
|
.swiper-slide{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.swiper-slide img{
|
||||||
|
display: block;
|
||||||
|
width: 600px;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.swiper{
|
||||||
|
width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.swiper-slide {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
.mySwiper2 {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.mySwiper {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px 0px;
|
||||||
|
}
|
||||||
|
.mySwiper .swiper-slide {
|
||||||
|
width: 500px;
|
||||||
|
height: 100px;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
.mySwiper .swiper-slide-thumb-active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.swiper-slide img {
|
||||||
|
display: block;
|
||||||
|
width: 850px;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
103
real_estate/client/src/pages/Listing.jsx
Normal file
103
real_estate/client/src/pages/Listing.jsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import {useParams} from 'react-router-dom'
|
||||||
|
import {Swiper, SwiperSlide} from 'swiper/react'
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import 'swiper/css';
|
||||||
|
import 'swiper/css/free-mode';
|
||||||
|
import 'swiper/css/navigation';
|
||||||
|
import 'swiper/css/thumbs';
|
||||||
|
import { FreeMode, Navigation, Thumbs } from 'swiper/modules';
|
||||||
|
import { BiSolidMap } from "react-icons/bi";
|
||||||
|
|
||||||
|
|
||||||
|
const Listing = () => {
|
||||||
|
const [thumbsSwiper, setThumbsSwiper] = useState(null);
|
||||||
|
const params = useParams();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [listing, setListing] = useState(null);
|
||||||
|
const { currentUser } = useSelector((state) => state.user);
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchListing = async() =>{
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const res = await fetch(`/server/listing/get/${params.listingId}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.success === false){
|
||||||
|
setError(true);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setListing(data);
|
||||||
|
setLoading(false);
|
||||||
|
setError(false)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setError(true);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchListing();
|
||||||
|
}, [params.listingId]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{loading && <p className="text-center my-10 text-xl text-blue-900">Loading...</p>}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
</Swiper>
|
||||||
|
<Swiper onSwiper={setThumbsSwiper} loop={true} spaceBetween={10} slidesPerView={5} freeMode={true} watchSlidesProgress={true} modules={[FreeMode, Navigation, Thumbs]} className="mySwiper">
|
||||||
|
{listing.imageUrls.map(url =>
|
||||||
|
<SwiperSlide key={url}><img src={url} alt="" /></SwiperSlide>
|
||||||
|
)}
|
||||||
|
</Swiper>
|
||||||
|
<div className="flex flex-col max-w-4xl mx-auto p-4 gap-4">
|
||||||
|
<p className="text-2xl font-semibold mt-5 capitalize">
|
||||||
|
{listing.name} - $ {''}
|
||||||
|
{listing.offer ? listing.discountPrice.toLocaleString('en-US') : listing.currentPrice.toLocaleString('en-US')}
|
||||||
|
{listing.type === 'rent' && ' / month'}
|
||||||
|
</p>
|
||||||
|
<p className='flex items-center mt-4 gap-2 text-gray-600 text-sm sm:text-md'><BiSolidMap className='text-blue-900 '/>{listing.address}</p>
|
||||||
|
|
||||||
|
<div className='flex flex-col justify-between gap-4'>
|
||||||
|
<ul className='font-semibold text-sm flex flex-wrap items-center gap-2'>
|
||||||
|
<li className='flex items-center gap-2 whitespace-nowrap '>
|
||||||
|
<div className='text-lg'> {listing.bed > 1 ? `${listing.bed} beds ` : `${listing.bed} bed `}</div>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center gap-2 whitespace-nowrap '>
|
||||||
|
<div className='text-lg'> | {listing.bath > 1? `${listing.bath} baths ` : `${listing.bath} bath `}</div>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center gap-2 whitespace-nowrap '>
|
||||||
|
<div className='text-lg'> | {listing.parking ? 'Garage' : 'No Garage'}</div>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center gap-2 whitespace-nowrap '>
|
||||||
|
<div className='text-lg'> | {listing.furnished ? 'Furnished' : 'Unfurnished'}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className="flex gap-2 text-center">
|
||||||
|
<p className='bg-red-900 w-full max-w-[80px] text-white text-center p-2 rounded-md text-xs'>{listing.type === 'rent' ? 'For Rent' : 'For Sale'}</p>
|
||||||
|
{listing.offer && (
|
||||||
|
<p className='bg-green-800 w-full max-w-[80px] text-white text-center p-2 rounded-md text-xs'>
|
||||||
|
${+listing.currentPrice - +listing.discountPrice} Off </p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className='text-slate-800 flex flex-col mb-6'>
|
||||||
|
<span className='font-semibold text-black mt-4 mb-2 text-xl'>About this property</span>
|
||||||
|
<span>{listing.description}</span>
|
||||||
|
</p>
|
||||||
|
<button className='bg-blue-950 text-white rounded-md uppercase hover:opacity-95 p-2 mb-10 text-sm'>Contact Agent</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Listing
|
Loading…
Add table
Reference in a new issue