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