created and completed listing page functionality

This commit is contained in:
Juthatip McDevitt 2024-03-14 21:40:32 -05:00
parent 0b792b751c
commit cd79548dfc
5 changed files with 170 additions and 2 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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/>}/>

View file

@ -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;
}

View 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