Property listing pages

This commit is contained in:
Brad Traversy
2024-07-10 07:38:50 -04:00
parent 5dd575e001
commit 0b1c9aaa02
8 changed files with 292 additions and 21 deletions
+1 -6
View File
@@ -356,12 +356,7 @@
</div>
<p class="text-gray-500 mb-4">
This is a beautiful apartment located near the commons
</p>
<p class="text-gray-500 mb-4">
We have a beautiful apartment located near the commons. It is a
2 bedroom apartment with a full kitchen and bathroom. It is
available for weekly or monthly rentals.
</p>
</p>
</div>
<div class="bg-white p-6 rounded-lg shadow-md mt-6">
+105 -2
View File
@@ -1,4 +1,107 @@
const PropertyPage = () => {
return <div>PropertyPage</div>;
import PropertyHeaderImage from '@/components/PropertyHeaderImage';
import PropertyDetails from '@/components/PropertyDetails';
import connectDB from '@/config/database';
import Property from '@/models/Property';
const PropertyPage = async ({ params }) => {
await connectDB();
const property = await Property.findById(params.id).lean();
return (
<>
<PropertyHeaderImage image={property.images[0]} />
<section className='bg-blue-50'>
<div className='container m-auto py-10 px-6'>
<div className='grid grid-cols-1 md:grid-cols-70/30 w-full gap-6'>
<PropertyDetails property={property} />
{/* <!-- Sidebar --> */}
<aside className='space-y-4'>
<button className='bg-blue-500 hover:bg-blue-600 text-white font-bold w-full py-2 px-4 rounded-full flex items-center justify-center'>
<i className='fas fa-bookmark mr-2'></i> Bookmark Property
</button>
<button className='bg-orange-500 hover:bg-orange-600 text-white font-bold w-full py-2 px-4 rounded-full flex items-center justify-center'>
<i className='fas fa-share mr-2'></i> Share Property
</button>
{/* <!-- Contact Form --> */}
<div className='bg-white p-6 rounded-lg shadow-md'>
<h3 className='text-xl font-bold mb-6'>
Contact Property Manager
</h3>
<form>
<div className='mb-4'>
<label
className='block text-gray-700 text-sm font-bold mb-2'
htmlFor='name'
>
Name:
</label>
<input
className='shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'
id='name'
type='text'
placeholder='Enter your name'
required
/>
</div>
<div className='mb-4'>
<label
className='block text-gray-700 text-sm font-bold mb-2'
htmlFor='email'
>
Email:
</label>
<input
className='shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'
id='email'
type='email'
placeholder='Enter your email'
required
/>
</div>
<div className='mb-4'>
<label
className='block text-gray-700 text-sm font-bold mb-2'
htmlFor='phone'
>
Phone:
</label>
<input
className='shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline'
id='phone'
type='text'
placeholder='Enter your phone number'
/>
</div>
<div className='mb-4'>
<label
className='block text-gray-700 text-sm font-bold mb-2'
htmlFor='message'
>
Message:
</label>
<textarea
className='shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 h-44 focus:outline-none focus:shadow-outline'
id='message'
placeholder='Enter your message'
></textarea>
</div>
<div>
<button
className='bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-full w-full focus:outline-none focus:shadow-outline flex items-center justify-center'
type='submit'
>
<i className='fas fa-paper-plane mr-2'></i> Send Message
</button>
</div>
</form>
</div>
</aside>
</div>
</div>
</section>
</>
);
};
export default PropertyPage;
+1 -1
View File
@@ -4,7 +4,7 @@ import connectDB from '@/config/database';
const PropertiesPage = async () => {
await connectDB();
const properties = await Property.find({});
const properties = await Property.find({}).lean();
return (
<section className='px-4 py-6'>
+99
View File
@@ -0,0 +1,99 @@
import {
FaBed,
FaBath,
FaRulerCombined,
FaTimes,
FaCheck,
FaMapMarker,
} from 'react-icons/fa';
const PropertyDetails = ({ property }) => {
return (
<main>
<div className='bg-white p-6 rounded-lg shadow-md text-center md:text-left'>
<div className='text-gray-500 mb-4'>{property.type}</div>
<h1 className='text-3xl font-bold mb-4'>{property.name}</h1>
<div className='text-gray-500 mb-4 flex align-middle justify-center md:justify-start'>
<FaMapMarker className='text-orange-700 mt-1 mr-1' />
<p className='text-orange-700'>
{property.location.street}, {property.location.city}{' '}
{property.location.state}
</p>
</div>
<h3 className='text-lg font-bold my-6 bg-gray-800 text-white p-2'>
Rates & Options
</h3>
<div className='flex flex-col md:flex-row justify-around'>
<div className='flex items-center justify-center mb-4 border-b border-gray-200 md:border-b-0 pb-4 md:pb-0'>
<div className='text-gray-500 mr-2 font-bold'>Nightly</div>
<div className='text-2xl font-bold text-blue-500'>
{property.rates.nightly ? (
`$${property.rates.nightly.toLocaleString()}`
) : (
<FaTimes className='text-red-700' />
)}
</div>
</div>
<div className='flex items-center justify-center mb-4 border-b border-gray-200 md:border-b-0 pb-4 md:pb-0'>
<div className='text-gray-500 mr-2 font-bold'>Weekly</div>
<div className='text-2xl font-bold text-blue-500'>
{property.rates.weekly ? (
`$${property.rates.weekly.toLocaleString()}`
) : (
<FaTimes className='text-red-700' />
)}
</div>
</div>
<div className='flex items-center justify-center mb-4 pb-4 md:pb-0'>
<div className='text-gray-500 mr-2 font-bold'>Monthly</div>
<div className='text-2xl font-bold text-blue-500'>
{property.rates.monthly ? (
`$${property.rates.monthly.toLocaleString()}`
) : (
<FaTimes className='text-red-700' />
)}
</div>
</div>
</div>
</div>
<div className='bg-white p-6 rounded-lg shadow-md mt-6'>
<h3 className='text-lg font-bold mb-6'>Description & Details</h3>
<div className='flex justify-center gap-4 text-blue-500 mb-4 text-xl space-x-9'>
<p>
<FaBed className='inline-block mr-2' /> {property.beds}{' '}
<span className='hidden sm:inline'>Beds</span>
</p>
<p>
<FaBath className='inline-block mr-2' /> {property.baths}{' '}
<span className='hidden sm:inline'>Baths</span>
</p>
<p>
<FaRulerCombined className='inline-block mr-2' />
{property.square_feet}{' '}
<span className='hidden sm:inline'>sqft</span>
</p>
</div>
<p className='text-gray-500 mb-4'>{property.description}</p>
</div>
<div className='bg-white p-6 rounded-lg shadow-md mt-6'>
<h3 className='text-lg font-bold mb-6'>Amenities</h3>
<ul className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 list-none space-y-2'>
{property.amenities.map((amenity, index) => (
<li key={index}>
<FaCheck className='inline-block text-green-600 mr-2' /> {amenity}
</li>
))}
</ul>
</div>
<div className='bg-white p-6 rounded-lg shadow-md mt-6'>
<div id='map'></div>
</div>
</main>
);
};
export default PropertyDetails;
+22
View File
@@ -0,0 +1,22 @@
import Image from 'next/image';
const PropertyHeaderImage = ({ image }) => {
return (
<section>
<div className='container-xl m-auto'>
<div className='grid grid-cols-1'>
<Image
src={`/images/properties/${image}`}
alt=''
className='object-cover h-[400px] w-full'
width={0}
height={0}
sizes='100vw'
priority={true}
/>
</div>
</div>
</section>
);
};
export default PropertyHeaderImage;
+60 -1
View File
@@ -17,8 +17,11 @@
"react-spinners": "^0.14.1"
},
"devDependencies": {
"@types/node": "20.14.10",
"@types/react": "18.3.3",
"postcss": "^8",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.1",
"typescript": "5.5.3"
}
},
"node_modules/@alloc/quick-lru": {
@@ -329,6 +332,34 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/node": {
"version": "20.14.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
"integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -593,6 +624,13 @@
"node": ">=4"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
@@ -2028,6 +2066,27 @@
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+4 -1
View File
@@ -18,7 +18,10 @@
"react-spinners": "^0.14.1"
},
"devDependencies": {
"@types/node": "20.14.10",
"@types/react": "18.3.3",
"postcss": "^8",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.1",
"typescript": "5.5.3"
}
}
-10
View File
@@ -1,6 +1,5 @@
[
{
"_id": "1",
"owner": "1",
"name": "Boston Commons Retreat",
"type": "Apartment",
@@ -45,7 +44,6 @@
"updatedAt": "2024-01-01T00:00:00.000Z"
},
{
"_id": "2",
"owner": "1",
"name": "Cozy Downtown Loft",
"type": "Apartment",
@@ -89,7 +87,6 @@
"updatedAt": "2024-01-02T00:00:00.000Z"
},
{
"_id": "3",
"owner": "2",
"name": "Luxury Condo with a View",
"type": "Condo",
@@ -135,7 +132,6 @@
"updatedAt": "2024-01-03T00:00:00.000Z"
},
{
"_id": "4",
"owner": "2",
"name": "Charming Cottage Getaway",
"type": "Cottage Or Cabin",
@@ -169,7 +165,6 @@
"updatedAt": "2024-01-04T00:00:00.000Z"
},
{
"_id": "5",
"owner": "3",
"name": "Modern Downtown Studio",
"type": "Studio",
@@ -205,7 +200,6 @@
"updatedAt": "2024-01-05T00:00:00.000Z"
},
{
"_id": "6",
"owner": "3",
"name": "Seaside Retreat",
"type": "House",
@@ -241,7 +235,6 @@
"updatedAt": "2024-01-06T00:00:00.000Z"
},
{
"_id": "7",
"owner": "4",
"name": "Rustic Cabin in the Woods",
"type": "Cottage Or Cabin",
@@ -276,7 +269,6 @@
"updatedAt": "2024-01-07T00:00:00.000Z"
},
{
"_id": "8",
"owner": "5",
"name": "Ski-In/Ski-Out Chalet",
"type": "Chalet",
@@ -311,7 +303,6 @@
"updatedAt": "2024-01-08T00:00:00.000Z"
},
{
"_id": "9",
"owner": "6",
"name": "Mountain View Retreat",
"type": "House",
@@ -347,7 +338,6 @@
"updatedAt": "2024-01-09T00:00:00.000Z"
},
{
"_id": "10",
"owner": "7",
"name": "Historic Downtown Loft",
"type": "Apartment",