Airbnb clone, handle 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.

We’re now ready to implement booking a house!

Let’s keep things simple now and let’s defer payments to later.

A person can book if the dates chosen do not overlap with another booking for that house.

First, instead of allowing to reserve before being logged in, like we have now:

pages/houses/[id].js

<button
  className='reserve'
  onClick={() => {
    setShowLoginModal()
  }}>
  Reserve
</button>

we do a check on the user property from the easy-peasy store, and if we’re not logged in, we’ll show a “Log in to Reserve” button instead.

First we need to import useStoreState:

pages/houses/[id].js

import { useStoreState } from 'easy-peasy'

then we declare, inside the component function body the loggedIn value:

pages/houses/[id].js

const loggedIn = useStoreState((state) => state.login.loggedIn)

Now we can use this inside our JSX:

pages/houses/[id].js

{
  loggedIn ? (
    <button
      className="reserve"
      onClick={() => {
        //todo: add code to reserve
      }}
    >
      Reserve
    </button>
  ) : (
    <button
      className="reserve"
      onClick={() => {
        setShowLoginModal()
      }}
    >
      Log in to Reserve
    </button>
  )
}

Now when the Reserve now button is clicked I want to trigger a function that calls an endpoint on the server to book the place.

Server side I’m going to add the booking to a new table in the database, which we’ll call bookings.

Once I’m done with that, I’ll create a way to check if a house is booked in a particular period, to avoid people booking days already booked.

We’ll integrate that with the calendar, so people can’t book places booked, and we’ll check if the dates are still valid when we actually go to book.

Let’s do it.

Let’s start with the model, in the model.js file:

model.js

class Booking extends Sequelize.Model {}

Booking.init(
  {
    id: {
      type: Sequelize.DataTypes.INTEGER,
      autoIncrement: true,
      primaryKey: true
    },
    houseId: { type: Sequelize.DataTypes.INTEGER, allowNull: false },
    userId: { type: Sequelize.DataTypes.INTEGER, allowNull: false },
    startDate: { type: Sequelize.DataTypes.DATEONLY, allowNull: false },
    endDate: { type: Sequelize.DataTypes.DATEONLY, allowNull: false }
  },
  {
    sequelize,
    modelName: 'booking',
    timestamps: true
  }
)

And export it:

export { sequelize, User, House, Booking }

It’s very similar to the models we already have.

Now like we did before, we can call Booking.sync() to create the table in the database.

You can add this line at the end of the model.js file.

Booking.sync({ alter: true })

Ok, now that we have the model ready for our data, we can go and implement the booking functionality in the frontend.

In pages/houses/[id].js I first import axios:

import axios from 'axios'

In the component code, I declare 2 new state variables:

const [startDate, setStartDate] = useState()
const [endDate, setEndDate] = useState()

and when the date picker tells us new dates have been set, we update them.

Change:

<DateRangePicker
  datesChanged={(startDate, endDate) => {
    setNumberOfNightsBetweenDates(
      calcNumberOfNightsBetweenDates(startDate, endDate)
    )
    setDateChosen(true)
  }}
/>

to:

<DateRangePicker
  datesChanged={(startDate, endDate) => {
    setNumberOfNightsBetweenDates(
      calcNumberOfNightsBetweenDates(startDate, endDate)
    )
    setDateChosen(true)
    setStartDate(startDate)
    setEndDate(endDate)
  }}
/>

Now when clicking the “Reserve” button I now invoke a new function, that POSTS data to the /api/reserve endpoint:

<button
  className='reserve'
  onClick={async () => {
    try {
      const response = await axios.post('/api/reserve', {
        houseId: house.id,
        startDate,
        endDate,
      })
      if (response.data.status === 'error') {
        alert(response.data.message)
        return
      }
      console.log(response.data)
    } catch (error) {
      console.log(error)
      return
    }
  }}
>
  Reserve
</button>

In this function I hit the /api/reserve API endpoint with Axios, passing the house id, the start date, and the end date. The user is figured out in the server side thanks to seessions.

I added some simple console.log() calls to figure out what the server responds.

The endpoint implementation

Here’s the endpoint implementation, which we create into pages/api/reserve.js:

import { User, Booking } from '../../model.js'

export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }

  const user_session_token = req.cookies.nextbnb_session
  if (!user_session_token) {
    res.status(401).end()
    return
  }

  User.findOne({ where: { session_token: user_session_token } }).then(
    (user) => {
      Booking.create({
        houseId: req.body.houseId,
        userId: user.id,
        startDate: req.body.startDate,
        endDate: req.body.endDate
      }).then(() => {
        res.writeHead(200, {
          'Content-Type': 'application/json'
        })
        res.end(JSON.stringify({ status: 'success', message: 'ok' }))
      })
    }
  )
}

Great! Now the booking data is stored successfully:

See the code on GitHub

Next part: Handle booked dates

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

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