Airbnb clone, view bookings

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.

After the booking and the payment, the person that purchases the holiday at a house is now redirected to the /bookings URL, where we now have a placeholder page.

That’s managed by the pages/bookings.js file.

Right now we added some placeholer code there:

pages/bookings.js

import Layout from '../components/Layout'

export default function Bookings() {
  return <Layout content={<p>TODO</p>} />
}

We’re going to list all the bookings made by the user.

We replicate a bit what we did in pages/index.js to list houses, except now we list bookings:

pages/bookings.js

import axios from 'axios'
import Head from 'next/head'
import { Booking, House } from '../model.js'

import Layout from '../components/Layout'

export default function Bookings(props) {
  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Your bookings</title>
          </Head>
          <h2>Your bookings</h2>

          <div className="bookings">
            {props.bookings.map((booking, index) => {
              const house = props.houses.filter(
                (house) => house.id === booking.houseId
              )[0]
              return (
                <div className="booking" key={index}>
                  <img src={house.picture} alt="House picture" />
                  <div>
                    <h2>
                      {house.title} in {house.town}
                    </h2>
                    <p>
                      Booked from {new Date(booking.startDate).toDateString()}{' '}
                      to {new Date(booking.endDate).toDateString()}
                    </p>
                  </div>
                </div>
              )
            })}
          </div>

          <style jsx>{`
            .bookings {
              display: grid;
              grid-template-columns: 100%;
              grid-gap: 40px;
            }

            .booking {
              display: grid;
              grid-template-columns: 30% 70%;
              grid-gap: 40px;
            }

            .booking img {
              width: 180px;
            }
          `}</style>
        </div>
      }
    />
  )
}

export async function getServerSideProps({ req, res, query }) {
  const bookings = await Booking.findAndCountAll()
  const houses = await House.findAndCountAll()

  return {
    props: {
      bookings: bookings.rows.map((booking) => {
        booking.dataValues.createdAt = '' + booking.dataValues.createdAt
        booking.dataValues.updatedAt = booking.dataValues.updatedAt + ''

        return booking.dataValues
      }),
      houses: houses.rows.map((house) => house.dataValues)
    }
  }
}

Awesome!

See how we get, in addition to the houses data, the bookings data.

Now all the upcoming bookings should show up:

We can also add a link to this page in the components/Header.js component.

Add:

<li>
  <Link href='/bookings'>
    <a>Bookings</a>
  </Link>
</li>

before the line:

<li>
  <a>Logged in</a>
</li>

Here’s the end result:

Only seeing your own bookings

There’s a big problem in pages/bookings.js, and I’m sure you already noticed.

This screen prints all the bookings. Our bookings, and all the bookings of other people using the site. Also, all bookings are shown even if you’re not logged in.

Let’s first make sure if we’re not logged in we can’t see this page.

First, import the cookies npm library.

import Cookies from 'cookies'

In getServerSideProps, import and get the cookie that we set when the user logs in:

const cookies = new Cookies(req, res)
const nextbnb_session = cookies.get('nextbnb_session')

If the cookie is not found, redirect to /:

if (!nextbnb_session) {
  res.writeHead(301, {
    Location: '/'
  })
  res.end()
  return { props: {} }
}

Now, also set the loggedIn easy-peasy state when loading this page from the server, like we do for the other pages.

Add

nextbnb_session: nextbnb_session || null,

to the returned props of getServerSideProps, then at the top import:

import { useEffect } from 'react'
import { useStoreActions } from 'easy-peasy'

And inside the Bookings component, first list the props that we accept:

export default function Bookings({ bookings, houses, nextbnb_session }) {

Then proceed to set the state based on the cookie value:

const setLoggedIn = useStoreActions((actions) => actions.login.setLoggedIn)

useEffect(() => {
  if (nextbnb_session) {
    setLoggedIn(true)
  }
}, [])

Now in getServerSideProps based on the session cookie value, we get the user and we filter its bookings:

const user = await User.findOne({
  where: { session_token: nextbnb_session }
})

bookings = await Booking.findAndCountAll({ where: { userId: user.id } })

This is the final code for pages/bookings.js:

import axios from 'axios'
import Head from 'next/head'
import Cookies from 'cookies'
import { Booking, House, User } from '../model.js'
import { useEffect } from 'react'
import { useStoreActions } from 'easy-peasy'

import Layout from '../components/Layout'

export default function Bookings({ bookings, houses, nextbnb_session }) {
  const setLoggedIn = useStoreActions((actions) => actions.login.setLoggedIn)

  useEffect(() => {
    if (nextbnb_session) {
      setLoggedIn(true)
    }
  }, [])

  return (
    <Layout
      content={
        <div>
          <Head>
            <title>Your bookings</title>
          </Head>
          <h2>Your bookings</h2>

          <div className="bookings">
            {bookings.map((booking, index) => {
              const house = houses.filter(
                (house) => house.id === booking.houseId
              )[0]
              return (
                <div className="booking" key={index}>
                  <img src={house.picture} alt="House picture" />
                  <div>
                    <h2>
                      {house.title} in {house.town}
                    </h2>
                    <p>
                      Booked from {new Date(booking.startDate).toDateString()}{' '}
                      to {new Date(booking.endDate).toDateString()}
                    </p>
                  </div>
                </div>
              )
            })}
          </div>

          <style jsx>{`
            .bookings {
              display: grid;
              grid-template-columns: 100%;
              grid-gap: 40px;
            }

            .booking {
              display: grid;
              grid-template-columns: 30% 70%;
              grid-gap: 40px;
            }

            .booking 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 bookings
  if (!nextbnb_session) {
    res.writeHead(301, {
      Location: '/'
    })
    res.end()
    return { props: {} }
  }

  const user = await User.findOne({
    where: { session_token: nextbnb_session }
  })

  bookings = await Booking.findAndCountAll({ where: { userId: user.id } })

  const houses = await House.findAndCountAll()

  return {
    props: {
      bookings: bookings
        ? bookings.rows.map((booking) => {
            booking.dataValues.createdAt = '' + booking.dataValues.createdAt
            booking.dataValues.updatedAt = booking.dataValues.updatedAt + ''
            return booking.dataValues
          })
        : null,
      houses: houses.rows.map((house) => house.dataValues),
      nextbnb_session: nextbnb_session || null
    }
  }
}

See the code on GitHub

Next part: Airbnb clone, clean bookings

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

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