We’re now ready to implement booking a house!

Let’s make 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, I show a “Log in to Reserve” button instead.

First we need to import useStoreState:

pages/houses/[id].js

import { useStoreActions, useStoreState } from 'easy-peasy'

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

pages/houses/[id].js

const user = useStoreState(state => state.user.user)

Now we can use this inside our JSX:

pages/houses/[id].js

{user ? (
  <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 models/booking.js file:

models/booking.js

const Sequelize = require('sequelize')
const sequelize = require('../database.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
  }
)

module.exports = Booking

It’s very similar to the models we already have, except we now have 2 date fields: startDate and endDate.

In server.js we now import and sync it to the database, to create the Postgres table automatically:

const Booking = require('./models/booking.js')

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'

(yes, I know we already import isomorphic-unfetch for SSR but I prefer using Axios when I can, and we already import it in other components, so.. no additional page weight for us!

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

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

and when the datepicker 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/houses/reserve endpoint:

<button
  className='reserve'
  onClick={async () => {
    try {
      const response = await axios.post('/api/houses/reserve', {
        houseId: props.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/houses/reserve server-side 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 server.js:

server.post('/api/houses/reserve', (req, res) => {
  const userEmail = req.session.passport.user
  User.findOne({ where: { email: userEmail } }).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:

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


Go to the next lesson