Real Estate Listings Platform using NextJS
In this article, we will explore the process of building a real estate listing platform using Next.js. real estate listings platform is a web application that aims to provide users with a comprehensive platform to browse and search for properties. The platform will cater to both buyers and sellers, offering a seamless experience for property listing, and searching.
Output Preview: Let us have a look at how the final output will look like.
Prerequisites:
Approach to Create Real Estate Listings Platform:
- Setup the Project by Creating a new NextJS project, Install the necessary libraries.
- Design the layout of Real Estate Listings platform, including components like Navbar, Listings, AddListings, etc.
- Create Listings component which will display the all home lists.
- Implement search and filter functionality in Listings component to search for specific properties and to filter listings based on types such as for rent or for sale.
- Create AddListing component that will allows users to add new listings with a form that includes fields for title, description, location, listing type, price, rent and an image upload field.
- We will utilize useState hook to manage the states of application.
- We will use Bootstrap to style components and create a visually appealing design for platform.
Steps to Create Real Estate Listing Platform:
Step 1: Create a application of NextJS using the following command.
npx create-next-app real-estate
Step 2: Navigate to project directory
cd real-estate
Step 3: Install the necessary package in your project using the following command.
npm install bootstrap
npm install react-icons --save
Step 4: Create the folder structure as shown below and create the files in respective folders.
Project Structure:
The updated dependencies in package.json file will look like:
"dependencies": {
"autoprefixer": "^10.4.18",
"bootstrap": "^5.3.3",
"next": "14.1.3",
"postcss": "^8.4.36",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.0.1"
}
Example: Implementation of the above approach using NextJS.
// page.js
import React from 'react'
import HomeListing
from './components/Listings'
const page = () => {
return (
<div>
<HomeListing />
</div>
)
}
export default page
// Navbar.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Link from 'next/link';
function Navbar() {
return (
<nav className="navbar navbar-expand-lg navbar-light bg-light shadow top-0">
<div className="container">
<a className="navbar-brand text-success" href="#">
GFG Estate
</a>
<button className="navbar-toggler"
type="button" data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link" href="/">Home</Link>
</li>
<li className="nav-item">
<Link className="nav-link"
href="AddListing">
Add New Listing
</Link>
</li>
</ul>
</div>
</div>
</nav>
);
}
export default Navbar;
// Listings.js
'use client';
import React, { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Navbar from './Navbar';
import { FaLocationDot } from "react-icons/fa6";
const HomeListing = () => {
// Retrieve listings from local storage
const storedListings = localStorage.getItem('listings');
const allListings = storedListings ? JSON.parse(storedListings) : [];
const [listings, setListings] = useState(allListings);
const [filter, setFilter] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const handleFilterChange = (e) => {
const value = e.target.value;
setFilter(value);
if (value === 'all') {
setListings(allListings);
} else if (value === 'rent') {
const rentListings =
allListings.filter(
(listing) =>
listing.listingType === 'rent');
setListings(rentListings);
} else if (value === 'sale') {
const saleListings =
allListings.filter(
(listing) =>
listing.listingType === 'sale');
setListings(saleListings);
}
};
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
const filteredListings =
allListings.filter((listing) =>
listing.title.toLowerCase()
.includes(e.target.value.toLowerCase())
);
setListings(filteredListings);
};
return (
<>
<Navbar />
<div className="container mt-5">
<div className="row">
<div className="col-md-3">
<div className="mb-3">
<p>Type:</p>
<label className="form-check-label me-3"
style={{ fontSize: '1.2rem' }}>
<input
type="checkbox"
name="filter"
value="all"
checked={filter === 'all'}
onChange={handleFilterChange}
style={{ transform: 'scale(1.5)' }} />
{' '}All
</label>
<label className="form-check-label me-3"
style={{ fontSize: '1.2rem' }}>
<input
type="checkbox"
name="filter"
value="rent"
checked={filter === 'rent'}
onChange={handleFilterChange}
style={{ transform: 'scale(1.5)' }} />
{' '}Rent
</label>
<label className="form-check-label"
style={{ fontSize: '1.2rem' }}>
<input
type="checkbox"
name="filter"
value="sale"
checked={filter === 'sale'}
onChange={handleFilterChange}
style={{ transform: 'scale(1.5)' }} />
{' '}Sale
</label>
</div>
<div className="mb-3">
<label htmlFor="search"
className="form-label">Search</label>
<input
type="text"
className="form-control"
id="search"
value={searchTerm}
placeholder='seacrh...'
onChange={handleSearchChange}
/>
</div>
</div>
<div className="col-md-9">
<div className="row">
{listings.map((listing) => (
<div key={listing.id}
className="col-lg-4 col-md-6 mb-4">
<div className="card">
<div className="card-overlay">
<img src={listing.imageData}
className="card-img-top im"
alt={listing.title}
style={{ height: '200px' }} />
</div>
<div className="card-body">
<h3 className="card-title">
{listing.title}
</h3>
<p className="card-text">
<FaLocationDot
className='text-success' />
{listing.location}
</p>
<div className="d-flex
justify-content-between">
<p className="card-text">
{
listing.listingType ===
'sale' ? 'For Sale' : 'For Rent'
}
</p>
{listing.listingType === 'sale' ? (
<p className="card-text">
₹{listing.price}
</p>
) : (
<p className="card-text">
₹{listing.rent}/month
</p>
)}
</div>
<button className='btn btn-outline-success w-100'>
Explore
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
<style jsx>{`
.card:hover {
border-radius: 8px;
transition: box-shadow 0.3s;
width: 101%;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
`}</style>
</>
);
};
export default HomeListing;
// AddListing.js
import React,
{
useState
} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Navbar
from '@/app/components/Navbar';
const AddListing = () => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [price, setPrice] = useState('');
const [rent, setRent] = useState('');
const [location, setLocation] = useState('');
const [image, setImage] = useState('');
const [imageData, setImageData] = useState('');
const [listingType, setListingType] = useState('sale');
// Default to sale
const handleSubmit = (e) => {
e.preventDefault();
console.log(
{
title, description,
price, rent,
location, image,
imageData, listingType
});
// Save the form data and image data to local storage
const listingData =
{
title, description,
price, rent,
location,
image, imageData,
listingType
};
const listings =
JSON.parse(
localStorage.getItem('listings')
) || [];
listings.push(listingData);
localStorage.setItem('listings', JSON.stringify(listings));
// Reset form fields after submission
setTitle('');
setDescription('');
setPrice('');
setRent('');
setLocation('');
setImage('');
setImageData('');
setListingType('sale');
};
const handleImageChange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onloadend = () => {
setImageData(reader.result);
};
if (file) {
reader.readAsDataURL(file);
setImage(file.name);
}
};
return (
<>
<Navbar />
<div className="container" style={{ width: "70%" }}>
<h2 className="mt-3 mb-4">Add New Listing</h2>
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="title"
className="form-label">
Title
</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={
(e) =>
setTitle(e.target.value)
}
required />
</div>
<div className="mb-3">
<label htmlFor="description"
className="form-label">
Description
</label>
<textarea
className="form-control"
id="description"
value={description}
onChange={
(e) =>
setDescription(e.target.value)
}
required
></textarea>
</div>
<div className="mb-3">
<label htmlFor="location"
className="form-label">
Location
</label>
<input
type="text"
className="form-control"
id="location"
value={location}
onChange={
(e) =>
setLocation(e.target.value)
}
required
/>
</div>
<div className="mb-3">
<label htmlFor="listingType"
className="form-label">
Listing Type
</label>
<select
className="form-control"
id="listingType"
value={listingType}
onChange={(e) => setListingType(e.target.value)}
required>
<option value="sale">For Sale</option>
<option value="rent">For Rent</option>
</select>
</div>
{listingType === 'sale' && (
<div className="mb-3">
<label htmlFor="price"
className="form-label">
Price
</label>
<input
type="text"
className="form-control"
id="price"
value={price}
onChange={(e) => setPrice(e.target.value)}
required />
</div>
)}
{listingType === 'rent' && (
<div className="mb-3">
<label htmlFor="rent"
className="form-label">
Rent per Month
</label>
<input
type="text"
className="form-control"
id="rent"
value={rent}
onChange={
(e) =>
setRent(e.target.value)
}
required
/>
</div>
)}
<div className="mb-3">
<label htmlFor="image"
className="form-label">Image</label>
<input
type="file"
className="form-control"
id="image"
accept="image/*"
onChange={handleImageChange}
required
/>
</div>
{imageData && (
<img src={imageData}
alt="Selected"
className="img-fluid mb-3"
style={{ maxWidth: '200px' }} />
)}
<button type="submit"
className="btn btn-primary">
Add Listing
</button>
</form>
</div>
</>
);
};
export default AddListing;
Start your application using the following command:
npm run dev
Output: Naviage to the URL http://localhost:3000: