Airbnb clone, managing houses

Join the 2022 Full-Stack Web Dev Bootcamp!


This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.

Now 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 houses and bookings list

Next part: Airbnb clone, adding a new house

Want to become a better Web Developer? Join the 2022 Web Development Bootcamp!

⭐️⭐️⭐️ Join the 2022 Web Development Bootcamp ⭐️⭐️⭐️