In this last module of the project we’re going to create the exprience for hosts, rather than for people looking to book hosts’s homes.
In particular, we’ll:
- let people add houses, set prices and details
- let people edit houses information
- let people see the bookings for their houses
Now when you are logged out, on the site, we have a “Become a host” menu in the Nav bar which we added when we started. I’m going to remove it, and instead add a menu to logged in users.
We’ll add this functionality under a new “Your houses” menu.
Let’s create the link in components/Header.js
<li>
<Link href='/host'>
<a>Your Houses</a>
</Link>
</li>
Now create a pages/host/index.js
file
I want to list the houses, and we’ll do in a way similar to how we list bookings in pages/bookings.js
:
pages/host/index.js
import axios from 'axios'
import Head from 'next/head'
import Link from 'next/link'
import Cookies from 'cookies'
import { House, User } from '../../model.js'
import { useEffect } from 'react'
import { useStoreActions } from 'easy-peasy'
import Layout from '../../components/Layout'
export default function Host({ houses, nextbnb_session }) {
const setLoggedIn = useStoreActions((actions) => actions.login.setLoggedIn)
useEffect(() => {
if (nextbnb_session) {
setLoggedIn(true)
}
}, [])
return (
<Layout
content={
<div>
<Head>
<title>Your houses</title>
</Head>
<h2>Your houses</h2>
<div className='houses'>
{houses
? houses.map((house, index) => {
return (
<div className='house' key={index}>
<img src={house.picture} alt='House picture' />
<div>
<h2>
{house.title} in {house.town}
</h2>
<p>
<Link href={`/houses/${house.id}`}>
<a>View house page</a>
</Link>
</p>
<p>
<Link href={`/host/${house.id}`}>
<a>Edit house details</a>
</Link>
</p>
</div>
</div>
)
})
: ''}
</div>
<style jsx>{`
.houses {
display: grid;
grid-template-columns: 100%;
grid-gap: 40px;
}
.house {
display: grid;
grid-template-columns: 30% 70%;
grid-gap: 40px;
}
.house img {
width: 180px;
}
`}</style>
</div>
}
/>
)
}
export async function getServerSideProps({ req, res, query }) {
const cookies = new Cookies(req, res)
const nextbnb_session = cookies.get('nextbnb_session')
let houses
if (!nextbnb_session) {
res.writeHead(301, {
Location: '/',
})
res.end()
return { props: {} }
}
const user = await User.findOne({
where: { session_token: nextbnb_session },
})
houses = await House.findAndCountAll({ where: { owner: user.id } })
return {
props: {
houses: houses ? houses.rows.map((house) => house.dataValues) : null,
nextbnb_session,
},
}
}
See, we look for the houses in getServerSideProps
calling House.findAndCountAll()
, limiting the houses to the ones that the user owns.
Now let’s also list the bookings for the houses a person manages.
We retrieve them by
Let’s add this capability to the API, first. For each house returned by House.findAll()
, we get the id
of the house, we add it to an array, and we gather all the bookings that are assigned to the user’s houses:
server.js
server.get('/api/host/list', async (req, res) => {
if (!req.session.passport || !req.session.passport.user) {
res.writeHead(403, {
'Content-Type': 'application/json',
})
res.end(
JSON.stringify({
status: 'error',
message: 'Unauthorized',
})
)
return
}
const userEmail = req.session.passport.user
const user = await User.findOne({ where: { email: userEmail } })
const houses = await House.findAll({
where: {
host: user.id,
},
})
const houseIds = houses.map((house) => house.dataValues.id)
const bookingsData = await Booking.findAll({
where: {
paid: true,
houseId: {
[Op.in]: houseIds,
},
endDate: {
[Op.gte]: new Date(),
},
},
order: [['startDate', 'ASC']],
})
const bookings = await Promise.all(
bookingsData.map(async (booking) => {
return {
booking: booking.dataValues,
house: houses.filter(
(house) => house.dataValues.id === booking.dataValues.houseId
)[0].dataValues,
}
})
)
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(
JSON.stringify({
bookings,
houses,
})
)
})
Now we can use this data in our pages/host/index.js
page. We first return the bookings from getInitialProps
, and then we display them, a little like we displayed the houses before.
I added a container HTML and some CSS to make things look good:
pages/host/index.js
import axios from 'axios'
import Head from 'next/head'
import Link from 'next/link'
import Layout from '../../components/Layout'
const Host = (props) => {
return (
<Layout
content={
<div>
<Head>
<title>Your houses</title>
</Head>
<div className='container'>
<div className='houses'>
<h2>Your houses</h2>
<div className='list'>
{props.houses.map((house, index) => {
return (
<div className='house' key={index}>
<img src={house.picture} alt='House picture' />
<div>
<h2>
{house.title} in {house.town}
</h2>
<p>
<Link href={`/houses/${house.id}`}>
<a>View house page</a>
</Link>
</p>
<p>
<Link href={`/host/${house.id}`}>
<a>Edit house details</a>
</Link>
</p>
</div>
</div>
)
})}
</div>
</div>
<div className='bookings'>
<h2>Your bookings</h2>
<div className='list'>
{props.bookings.map((booking, index) => {
return (
<div class='booking' key={index}>
<div>
<h2>
{booking.house.title} in {booking.house.town}
</h2>
<p>
Booked from{' '}
{new Date(booking.booking.startDate).toDateString()}{' '}
to {new Date(booking.booking.endDate).toDateString()}
</p>
</div>
</div>
)
})}
</div>
</div>
</div>
<style jsx>{`
.container {
display: grid;
grid-template-columns: 60% 40%;
grid-gap: 50px;
}
.list {
display: grid;
grid-template-columns: 100%;
grid-gap: 40px;
margin-top: 60px;
}
.house {
display: grid;
grid-template-columns: 30% 70%;
grid-gap: 40px;
}
.house img {
width: 100px;
}
`}</style>
</div>
}
/>
)
}
Host.getInitialProps = async (ctx) => {
const response = await axios({
method: 'get',
url: 'http://localhost:3000/api/host/list',
headers: ctx.req ? { cookie: ctx.req.headers.cookie } : undefined,
})
return {
houses: response.data.houses,
bookings: response.data.bookings,
}
}
export default Host
That’s it! The houses and bookings list should be working, and it should look like this:
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs/commit/bda2a34867d25d1dad64ba0a24d8766c7200c7c5