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 Layout from '../../components/Layout'

const Host = props => {
  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Your houses</title>
          </Head>
          <h2>Your houses</h2>

          <div className='houses'>
            {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>

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

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

export default Host

We use the same “trick” with the cookie that we used in the last lesson of the previous module, to make sessions work with SSR in Next.js.

Now we create an endpoint GET /api/host/list in server.js to return the list of houses managed by a person:

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
    }
  })

  res.writeHead(200, {
    'Content-Type': 'application/json'
  })
  res.end(
    JSON.stringify({
      houses
    })
  )
})

See how I send houses as a distinct object.

This is because I also want to send, using this endpoint, the bookings for the houses a person manages.

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

The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs/commit/bda2a34867d25d1dad64ba0a24d8766c7200c7c5


Go to the next lesson