LOGIN AND SIGNUP FORMS
When people click the “Reserve” button, we’re now going to show a modal, to let people log in to the site.
Like they do on Airbnb, when you’re not logged in:
Right now we don’t have the option to log in, so people can’t be logged in - we don’t have a lot of logic we must implement. Let’s just show up a modal with the registration form.
Then we’ll add a “Already registered? Login in” link, which will just show the login and password fields.
We’ll skip the workflow to reset the password, which is something you’d need in production, but we can avoid now.
Let’s make the modal form.
We start simple, by creating a test modal. Make a  components/Modal.js  file, with this content:
components/Modal.js
export default function Modal(props) {
  return (
    <div className='nav-container'>
      <div
        className='modal-background'
        onClick={() => console.log('close')}
      ></div>
      <div className='modal'>{props.children}</div>
      <style jsx global>{`
        .modal-background {
          position: fixed;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background: rgba(0, 0, 0, 0.3);
        }
        .modal {
          position: absolute;
          left: 50%;
          top: 50%;
          width: calc(100vw - 4em);
          max-width: 32em;
          max-height: calc(100vh - 4em);
          overflow: auto;
          transform: translate(-50%, -50%);
          padding: 1em;
          border-radius: 0.2em;
          background: white;
        }
      `}</style>
    </div>
  )
}
This makes a simple modal component, that once imported can be used like this:
<Modal>test</Modal>
and it will render “test” inside a modal.
That’s what we’re going to do.
Open  components/Layout.js , add
components/Layout.js
import { useState } from 'react'
import Modal from './Modal'
and then inside the component body, add
components/Layout.js
const [showModal, setShowModal] = useState(true)
and add this in the JSX, after the  main  tag:
components/Layout.js
{
  showModal && <Modal>test</Modal>
}
Here’s the code:
import Header from './Header'
import { useState } from 'react'
import Modal from './Modal'
export default function Layout(props) {
  const [showModal, setShowModal] = useState(true)
  return (
    <div>
      <Header />
      <main>{props.content}</main>
      {showModal && <Modal>test</Modal>}
      <style jsx>{`
        main {
          position: relative;
          max-width: 56em;
          background-color: white;
          padding: 2em;
          margin: 0 auto;
          box-sizing: border-box;
        }
      `}</style>
    </div>
  )
}
This is the result that you should have if you try reloading the page:
Awesome! Now we know the modal is working, in terms of the basic functionality we need.
If you click outside of the modal, nothing happens but you’ll get a “close” string in the console because we have this line:
components/Modal.js
<div className='modal-background' onClick={() => console.log('close')}></div>
Instead, let’s invoke a  close  prop, which we assume it’s a function passed to us by the parent component ( Layout ). We’ll handle closing the modal in that component, by using the  setShowModal  hook.
components/Modal.js
<div className='modal-background' onClick={() => props.close()}></div>
components/Layout.js
{
  showModal && <Modal close={() => setShowModal(false)}>test</Modal>
}
Now you should be able to close the modal clicking outside it!
Now let’s create 2 specialized modals:  components/RegistrationModal.js  and  components/LoginModal.js .
In their content, just add
components/RegistrationModal.js
export default function RegistrationModal(props) {
  return <p>Registration Modal</p>
}
and
components/LoginModal.js
export default function LoginModal(props) {
  return <p>Login Modal</p>
}
Everything we write inside the opening and closing  <Modal>  tags will be rendered in the modal, because we used  props.children  in the  Modal  component JSX, so just as we entered the  test  string, we can add another component.
In particular, we can add (depending on our goal) the  RegistrationModal  or the  LoginModal  components.
We add 2 more items in the Layout component state, using hooks like we did for  showModal :
components/Layout.js
const [showLoginModal, setShowLoginModal] = useState(true)
const [showRegistrationModal, setShowRegistrationModal] = useState(false)
Notice I set  showLoginModal  to default to  true , so we can default to showing the login modal for our testing purposes.
Now in the Layout component, import LoginModal and RegistrationModal:
In the JSX instead of
{
  showModal && <Modal>test</Modal>
}
we embed the LoginModal or RegistrationModal components depending on the component state:
{
  showModal && (
    <Modal close={() => setShowModal(false)}>
      {showLoginModal && <LoginModal />}
      {showRegistrationModal && <RegistrationModal />}
    </Modal>
  )
}
This should already display the  LoginModal  component in the modal.
Now let’s define this  LoginModal  component in details. We want to define a form with an email and password fields:
components/LoginModal.js
export default function LoginModal(props) {
  return (
    <>
      <h2>Log in</h2>
      <div>
        <form>
          <input id='email' type='email' placeholder='Email address' />
          <input id='password' type='password' placeholder='Password' />
          <button>Log in</button>
        </form>
      </div>
    </>
  )
}
This is just some basic HTML. We need some CSS, but since we are going to use the same CSS in the registration modal too, let’s add the CSS in the  styles/globals.css  file as part of the global styles:
button {
  background-color: rgb(255, 90, 95);
  color: white;
  font-size: 13px;
  width: 100%;
  border: none;
  height: 40px;
  border-radius: 4px;
  cursor: pointer;
}
input[type='text'],
input[type='email'],
input[type='password'] {
  display: block;
  padding: 20px;
  font-size: 20px !important;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
  margin-bottom: 10px;
}
Notice how the  button  stying is the same as the one we added in  pages/houses/[id].js  to style the “Reserve” button, so we can remove the corresponding CSS from that file, to avoid redundancy.
Things should look pretty good by now:
The registration component is going to be very similar:
export default function RegistrationModal(props) {
  return (
    <>
      <h2>Sign up</h2>
      <div>
        <form>
          <input id='email' type='email' placeholder='Email address' />
          <input id='password' type='password' placeholder='Password' />
          <input
            id='passwordconfirmation'
            type='password'
            placeholder='Enter password again'
          />
          <button>Sign up</button>
        </form>
      </div>
    </>
  )
}
Now we add a way to go from one form to another by adding a link at the bottom of each form:
<p>
  Don't have an account yet?{' '}
  <a href='javascript:;' onClick={() => props.showSignup()}>
    Sign up
  </a>
</p>
and
<p>
  Already have an account?{' '}
  <a href='javascript:;' onClick={() => props.showLogin()}>
    Log in
  </a>
</p>
I used  javascript:;  as the  href  value, to tell the browser we’ll use JS to handle the click, and simultaneously avoid the URL to change if I use  href="#" . See https://flaviocopes.com/links-for-javascript/ for more info on this method I used.
Notice how we call  props.showSignup()  and  props.showLogin() . Those are 2 functions we pass as props from the parent component,  Layout .
Now in  Layout.js  we handle for each component the corresponding prop:
{
  showModal && (
    <Modal close={() => setShowModal(false)}>
      {showLoginModal && (
        <LoginModal
          showSignup={() => {
            setShowRegistrationModal(true)
            setShowLoginModal(false)
          }}
        />
      )}
      {showRegistrationModal && (
        <RegistrationModal
          showLogin={() => {
            setShowRegistrationModal(false)
            setShowLoginModal(true)
          }}
        />
      )}
    </Modal>
  )
}
You can try the frontend, you should be able to switch between the forms:
Remove the  text-decoration: none;  line from the  a  tag in  styles/globals.css  to make the link have an underline.
Let’s add a form submit event, by adding an event handler to the  onSubmit  event on the  form  tags in both forms:
<form
	onSubmit={event => {
		alert('Log in!')
		event.preventDefault()
	}}>
	...
and in the registration form:
<form
	onSubmit={event => {
		alert('Sign up!')
		event.preventDefault()
	}}>
	...
Here’s the full  LoginModal.js  component
export default function LoginModal(props) {
  return (
    <>
      <h2>Log in</h2>
      <div>
        <form
          onSubmit={(event) => {
            alert('Sign up!')
            event.preventDefault()
          }}
        >
          <input id='email' type='email' placeholder='Email address' />
          <input id='password' type='password' placeholder='Password' />
          <button>Log in</button>
        </form>
      </div>
      <p>
        Don't have an account yet?{' '}
        <a href='javascript:;' onClick={() => props.showSignup()}>
          Sign up
        </a>
      </p>
    </>
  )
}
and here’s the full  RegistrationModal.js  component:
export default function RegistrationModal(props) {
  return (
    <>
      <h2>Sign up</h2>
      <div>
        <form
          onSubmit={(event) => {
            alert('Log in!')
            event.preventDefault()
          }}
        >
          <input id='email' type='email' placeholder='Email address' />
          <input id='password' type='password' placeholder='Password' />
          <input
            id='passwordconfirmation'
            type='password'
            placeholder='Enter password again'
          />
          <button>Sign up</button>
        </form>
      </div>
      <p>
        Already have an account?{' '}
        <a href='javascript:;' onClick={() => props.showLogin()}>
          Log in
        </a>
      </p>
    </>
  )
}
Great!
In the next lesson we’ll link these modals to the rest of the application.
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/4-2
ACTIVATE THE MODAL
Let’s first hide the form, which now loads by default on every page load.
We just need to default to  false  the hooks we defined in  components/Layout.js .
Instead of:
const [showModal, setShowModal] = useState(true)
const [showLoginModal, setShowLoginModal] = useState(true)
We set:
const [showModal, setShowModal] = useState(false)
const [showLoginModal, setShowLoginModal] = useState(false)
Now, we are going to enable the modals when these event occur:
- the  Sign Up  or  Log in  links are clicked in the nav bar, in  components/Header.js
- the  Reserve  button in the  pages/houses/[id].jscomponent is clicked after a user chooses a set of dates
It’s just 3 different places, but we can immediately spot a problem: the state of the modals is being centralized and we’d need to pass around both the state and the functions to update it, too much.
And we’re just starting out.
Soon we’ll have to manage the user logins, and that would also need more state management.
So, we’re going to add one library to our project, one library that helps us manage the state easily .
There are solutions in React that are quite complicated, and I’m sure they are useful in many scenarios, but I like to keep my code as simple as possible.
Simple is understandable.
Simple is beautiful.
Complexity should be avoided at all costs, and if possible hidden away in libraries that expose a simple interface to us.
It’s the case of this library, which is called  easy-peasy .
Go take a look at their website https://easy-peasy.now.sh/ and then come back.
First of all, stop the Next.js server and run
npm install easy-peasy
to install the library.
Then restart the Next.js server with  npm run dev .
Now, first of all we need to create a store . The store is the place where we’ll store our state, and the functions needed to modify it.
Create the store in the file  store.js  in the root of the project, with this content:
store.js
import { createStore, action } from 'easy-peasy'
export default createStore({})
We’ll add more things to this file later.
Now we need to do one thing - we need to wrap all the Next.js app into a component provided by easy-peasy, and the way Next.js provides us to do it is to create a file called  _app.js  in the  pages  folder.
Open  pages/_app.js , which now has this content:
pages/_app.js
import App from 'next/app'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}
export default MyApp
Now we’re going to import the store we defined in  store.js , and we also import the  StoreProvider  component from  easy-peasy .
With this component, we wrap the default  Component  and we pass the store as a prop to it:
pages/_app.js
import '../styles/globals.css'
import { StoreProvider } from 'easy-peasy'
import store from '../store'
function MyApp({ Component, pageProps }) {
  return (
    <StoreProvider store={store}>
      <Component {...pageProps} />
    </StoreProvider>
  )
}
export default MyApp
This operation makes now our store available in every component of the app .
So let’s now centralize the state we added to  components/Layout.js  in the last lesson, to the  store.js  file.
If you think we wasted some time in the last lesson, we didn’t - most of the times those implementations are iterative. You first try the simplest solution, and then move on to more complex scenarios as the needs evolve. Now we know what things we need.
store.js
import { createStore, action } from 'easy-peasy'
export default createStore({
  modals: {
    showModal: false,
    showLoginModal: false,
    showRegistrationModal: false,
    setShowModal: action((state) => {
      state.showModal = true
    }),
    setHideModal: action((state) => {
      state.showModal = false
    }),
    setShowLoginModal: action((state) => {
      state.showModal = true
      state.showLoginModal = true
      state.showRegistrationModal = false
    }),
    setShowRegistrationModal: action((state) => {
      state.showModal = true
      state.showLoginModal = false
      state.showRegistrationModal = true
    }),
  },
})
We defined a  modals  object with some properties, and 4 actions, which we’ll use in our app components to change the state.
Let’s start from the  Header.js  component. When our Log in and Sign up buttons are clicked, we want to activate the correct modal.
In there, we import  useStoreActions  to be able to access the store functions:
import { useStoreActions } from 'easy-peasy'
and inside the component we initialize those actions to be used:
const setShowLoginModal = useStoreActions(
  (actions) => actions.modals.setShowLoginModal
)
const setShowRegistrationModal = useStoreActions(
  (actions) => actions.modals.setShowRegistrationModal
)
Now we can call  setShowLoginModal  and  setShowRegistrationModal  as regular functions, and this is what we’re going to do:
<nav>
  <ul>
    <li>
      <a href='#' onClick={() => setShowRegistrationModal()}>
        Sign up
      </a>
    </li>
    <li>
      <a href='#' onClick={() => setShowLoginModal()}>
        Log in
      </a>
    </li>
  </ul>
</nav>
Great! Now switch to the  components/Layout.js  file. In there, we import the  useStoreState  and  useStoreActions  from  easy-peasy .
import { useStoreState, useStoreActions } from 'easy-peasy'
useStoreState  is new to us, and we’ll use it to access the store state properties.
Inside the Layout component function body, let’s initialize a few variables:
const showModal = useStoreState((state) => state.modals.showModal)
const showLoginModal = useStoreState((state) => state.modals.showLoginModal)
const showRegistrationModal = useStoreState(
  (state) => state.modals.showRegistrationModal
)
const setHideModal = useStoreActions((actions) => actions.modals.setHideModal)
const setShowRegistrationModal = useStoreActions(
  (actions) => actions.modals.setShowRegistrationModal
)
const setShowLoginModal = useStoreActions(
  (actions) => actions.modals.setShowLoginModal
)
The first 3 are properties, which we’ll use to determine if modals should be shown or not, just like we did before using the properties generated using the  useState  hook:
{
  showModal && (
    <Modal close={() => setHideModal()}>
      {showLoginModal && (
        <LoginModal
          showSignup={() => {
            setShowRegistrationModal()
          }}
        />
      )}
      {showRegistrationModal && (
        <RegistrationModal
          showLogin={() => {
            setShowLoginModal()
          }}
        />
      )}
    </Modal>
  )
}
See, I called  setHideModal() . Before I had  setShowModal(false) , but I think  setHideModal()  is clearer. We could have passed a parameter as part of our  easy-peasy  action, too.
And instead of calling
setShowRegistrationModal(true)
setShowLoginModal(false)
I called
setShowRegistrationModal()
because we abstract away all the logic in the store. We don’t need to manage all the details, we just tell it to show the registration modal.
Same for the login modal.
The other part where we’ll show a modal, as we said, is the  Reserve  button in the  pages/houses/[id].js  component.
Users click the button when they finally chose the dates for the stay, and we can go on with the purchase flow.
So let’s now switch to the  pages/houses/[id].js  file.
In there, we first import  useStoreActions :
import { useStoreActions } from 'easy-peasy'
pages/houses/[id].js
and in the component body we initialize the  setShowLoginModal  action:
const setShowLoginModal = useStoreActions(
  (actions) => actions.modals.setShowLoginModal
)
and finally we call it when the button is clicked:
<button
  className='reserve'
  onClick={() => {
    setShowLoginModal()
  }}
>
  Reserve
</button>
Awesome! Our modal should be correctly working now. We’re ready to start implementing the registration functionality now.
Oh, one thing to note: I changed the links I previously defined as  <a href="javascript:;" ...  to  <a href="#" ...  because I noticed that React complained about using  javascript:  URLs in the DevTools console, about them being deprecated (although they are a perfectly find JavaScript feature). A reminder to always check the DevTools if React tells us something is wrong.
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/4-3
SEND REGISTRATION DATA TO THE SERVER
In this lesson we’re going to add and set up authentication into the application, so we can manage user logins and registrations.
But first, let me do a little bit of analysis here.
What’s the authentication story of our application? Let’s talk about it.
We’re going to use a database to store our users data (no Firebase, Auth0 or other cloud-based solutions).
We are NOT going to use OAuth (which serves a different purpose) or JWT tokens (which are better suited for API access tokens).
We’ll use what I believe is the most sensible solution to a simple authentication strategy: server based sessions, stored in HTTP-only cookies.
First, we’re going to add an API route to the Next.js application.
By convention Next.js considers a server route any JavaScript file under  pages/api/ .
Create the file  pages/api/auth/register.js . In there, we initialize and export a function:
pages/api/auth/register.js
export default (req, res) => {}
I only want to respond to POST requests, so we filter out requests that do not have this HTTP method:
pages/api/auth/register.js
export default (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  console.log('POST request received')
}
We’ll now use the Axios npm library to POST to this route to create a new user.
Open  components/RegistrationModal.js .
We create 3 state properties in the component, add we’ll bind them to the form elements.
Let’s import  useState  from  react :
import { useState } from 'react'
then we create 3 state properties, one for each element in the form:
components/RegistrationModal.js
export default (props) => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [passwordconfirmation, setPasswordconfirmation] = useState('')
  return <>...</>
}
Now to each form item, we add an  onChange  event listener, which uses the corresponding hook updater function to set the state value when the user types into the form fields:
components/RegistrationModal.js
import { useState } from 'react'
export default (props) => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [passwordconfirmation, setPasswordconfirmation] = useState('')
  return (
    <>
      <h2>Sign up</h2>
      <div>
        <form
          onSubmit={(event) => {
            alert('Sign up!')
            event.preventDefault()
          }}
        >
          <input
            id='email'
            type='email'
            placeholder='Email address'
            onChange={(event) => setEmail(event.target.value)}
          />
          <input
            id='password'
            type='password'
            placeholder='Password'
            onChange={(event) => setPassword(event.target.value)}
          />
          <input
            id='passwordconfirmation'
            type='password'
            placeholder='Enter password again'
            onChange={(event) => setPasswordconfirmation(event.target.value)}
          />
          <button>Sign up</button>
        </form>
        <p>
          Already have an account?{' '}
          <a href='#' onClick={() => props.showLogin()}>
            Log in
          </a>
        </p>
      </div>
    </>
  )
}
Great! When the form is submitted, now, we can console.log the values of the state to make sure they are updated, as we expect:
components/RegistrationModal.js
<form
  onSubmit={event => {
    console.log(email, password, passwordconfirmation)
    event.preventDefault()
  }}>
If you try to sign up using the website, you should see the values you entered printed in the browser console.
Now go to the terminal and install a the Axios npm package:
npm install axios
Then run  npm run dev  again to start the Next.js app.
Now switch back to the editor and include Axios in the RegistrationModal component:
components/RegistrationModal.js
import axios from 'axios'
and in the form  onSubmit  event handler function, let’s send those form values to the server, to the  auth/register  endpoint:
const submit = async () => {
  const response = await axios.post('/api/auth/register', {
    email,
    password,
    passwordconfirmation,
  })
  console.log(response)
}
and use this function on the submit event:
<form onSubmit={submit}>
See? We use the  /api/auth/register  route, which directly points to the  pages/api/auth/register.js  file.
Unfamiliar with Axios? See my Axios tutorial
Great! Now if you try to fill the registration form and submit the data:
you’ll see the server prints “POST request received” in the terminal where  npm run dev  started.
Now in the API endpoint let’s add  console.log(req.body)  and let’s also send a 200 response back to the client:
pages/api/auth/register.js
export default (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  console.log(req.body)
  res.end()
}
In the terminal, you’ll see the data coming in correctly to the server:
And in the client a successful response.
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/4-4
ADD POSTGRES
In this lesson we’re going to install Postgres locally on the computer, and set it up.
We’re also going to create the first table, to host our users data.
Let’s start! The instruction to install Postgres are based on macOS.
Search “how to install postgres on windows” or “how to install postgres on  your linux distribution ” if you’re using other platforms. It should not differ a lot, especially past the installation phase.
On macOS we’ll use Homebrew. If you don’t have Homebrew installed yet, go to https://brew.sh/ and follow the instructions there.
Once you are done, get back and in the command line run:
brew install postgresql
and after it finished, run:
brew services start postgresql
to start Postgres as a daemon, which means it will keep running in the background, listening for connections.
postgresql  is the more complex to pronounce name of Postgres, but they are the same thing. It just embeds SQL in the name. What’s  SQL ? SQL, pronouced “sequel”, means Structured Query Language, and it’s a special language we use to interact with a  relational database .
If you’re new to database, it’s a lot of new terms for you! Basically a relational database organizes the data into tables , and provides a way to insert and extract data from those tables. That’s SQL.
And we’re going to use it soon.
Right after we log in to Postgres!
Go back to the command line, and type
psql postgres
This will give you access to the  postgres  database, which is created by default, with your macOS username. Homebrew automatically created your user at installation.
Now that we are into the  psql  application, we can create a new database:
CREATE DATABASE nextbnb;
Don’t forget the semicolon
;, because it’s needed by SQL otherwise the command will not run.
Now in a new line, we switch to this database using
\c nextbnb
The prompt will tell something like this:
You are now connected to database "nextbnb" as user "<your username >". .
Now we’re going to create a new table.
Use this syntax:
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL,
  session_token VARCHAR(255),
  session_expiration TIMESTAMP
);
Now if you run this, and now error shows up, you will have the table in the system.
You can see it by running the command
\dt
which will show you the database tables:
If you did any error, you can delete the table by running the command
DROP TABLE users
To finally quit  psql , run
\q
Now that you know how things work under the hood, I will show you an easier way to work with Postgres, on macOS: the Postico app:
It’s handy especially to keep track of the data inserted, and to edit table fields without messing directly with the SQL.
There are of course many alternatives for Windows and Linux, you can Google for those.
IMPLEMENT MODEL AND DB CONNECTION
In this lesson we’re going to add the user to the Postgres table when they fill the registration form, and we’re going to create a session.
The user will be immediately logged in when they register.
Let’s do it!
First, I want to simulate the registration form, because I don’t want to keep using the website interface to test our functionality.
I do so using Insomnia, a powerful application to work with network requests, for HTTP requests and also for GraphQL (it’s cross platform, works on Mac / Windows / Linux).
Remember that in the registration form we have those 3 fields:
- email
- password
- passwordconfirmation
and we submit those to the  /api/auth/register  server-side endpoint, as JSON data.
Let’s replicate this in an Insomnia request:
If all went well, the server should now print the data you sent in.
Let’s make the connection to Postgres now, so we can store the data in the  users  table.
Install the  pg  and  sequelize  npm packages:
npm install pg sequelize
pg  handles the Postgres connection. Sequelize instead is an ORM, a tool that makes working with a database much, much easier than working with plain SQL.
Then create a  database.js  file with this content:
export const user = '<username>'
export const password = ''
export const host = 'localhost'
export const database = 'nextbnb'
and change the  user  variable with your username.
In this file we’ll just store the database credentials.
Next, create a  model.js  file, and import those 3 objects from  sequelize :
import { Sequelize, Model, DataTypes } from 'sequelize'
We initialize a Sequelize instance passing the data to access our database, imported from  database.js , telling it which kind of database this is in the  dialect  property (it can handle more than just Postgres).
We also disable logging, because it can be very verbose as it logs all the SQL queries, which we don’t really need to look at (unless you’re debugging a problem):
import { Sequelize, Model, DataTypes } from 'sequelize'
import { user, password, host, database } from './database.js'
const sequelize = new Sequelize(database, user, password, {
  host,
  dialect: 'postgres',
  logging: false,
})
Then we create a model for our  users  table, describing the data it contains and the rules we want to apply. In this case, we disable  null , to always require an email and password:
export class User extends Model {}
User.init(
  {
    email: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    session_token: {
      type: DataTypes.STRING,
    },
    session_expiration: {
      type: DataTypes.DATEONLY,
    },
  },
  {
    sequelize,
    modelName: 'user',
    timestamps: false,
  }
)
I also tell  timestamps: false  otherwise we’d get an error, as Sequelize expects the  createdAt  and  updatedAt  fields in the table, which we don’t have. You could add them, but it’s unnecessary I think.
Now we use this model in the  pages/api/auth/register.js  file. We import it from the  model.js  file:
import { User } from '../../../model.js'
and then we call  User.create()  when we get a POST request, to add the user to the database.
I use  await  because this method returns a promise, so I also add  async  to the function assigned to  post :
pages/api/auth/register.js
import { User } from '../../../model.js'
export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  const { email, password, passwordconfirmation } = req.body
  const user = await User.create({ email, password })
  res.end(JSON.stringify({ status: 'success', message: 'User added' }))
}
Great! So now if you try to send the data using Insomnia, you’ll see the data into the table:
and a JSON message back:
{
  "status": "success",
  "message": "User added"
}
I want to check if the password confirmation matches the password, and if not I’ll show an error and halt the user registration process:
pages/api/auth/register.js
if (password !== passwordconfirmation) {
  res.end(
    JSON.stringify({ status: 'error', message: 'Passwords do not match' })
  )
  return
}
Here’s the full code so far
pages/api/auth/register.js
import { User } from '../../../model.js'
export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  const { email, password, passwordconfirmation } = req.body
  if (password !== passwordconfirmation) {
    res.end(
      JSON.stringify({ status: 'error', message: 'Passwords do not match' })
    )
    return
  }
  const user = await User.create({ email, password })
  res.end(JSON.stringify({ status: 'success', message: 'User added' }))
}
We need to do something important now: we need to encrypt the password stored in the database .
You never store passwords as plain text, as it is very dangerous for your reputation and your users.
We’re going to add a configuration to Sequelize to automatically encrypt passwords using  bcrypt .
Install bcrypt using npm:
npm install bcrypt
(it might take a little while)
now import  bcrypt  in the  model.js  file:
import bcrypt from 'bcrypt'
and add this code to the second argument you pass to  User.init()  in the  model.js  file:
model.js
hooks: {
  beforeCreate: async (user) => {
    const saltRounds = 10
    const salt = await bcrypt.genSalt(saltRounds)
    user.password = await bcrypt.hash(user.password, salt)
  }
}
We’re also going to add a new method to the User object prototype, which we call  isPasswordValid() :
User.prototype.isPasswordValid = async function (password) {
  return await bcrypt.compare(password, this.password)
}
which we’ll use later on to verify the password is correct.
The full code should be:
model.js
import { Sequelize, Model, DataTypes } from 'sequelize'
import { user, password, host, database } from './database.js'
import bcrypt from 'bcrypt'
const sequelize = new Sequelize(database, user, password, {
  host,
  dialect: 'postgres',
  logging: false,
})
class User extends Model {}
User.init(
  {
    email: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    session_token: {
      type: DataTypes.STRING,
    },
    session_expiration: {
      type: DataTypes.DATEONLY,
    },
  },
  {
    sequelize,
    modelName: 'user',
    timestamps: false,
    hooks: {
      beforeCreate: async (user) => {
        const saltRounds = 10
        const salt = await bcrypt.genSalt(saltRounds)
        user.password = await bcrypt.hash(user.password, salt)
      },
    },
  }
)
User.prototype.isPasswordValid = async function (password) {
  return await bcrypt.compare(password, this.password)
}
export { sequelize, User }
In the next lesson we’ll go on with handling the session.
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/5-2
CREATE A SESSION TOKEN WHEN REGISTERING THE USER
We worked on registration in the previous module, introducing a POST call to the  /api/auth/register  URL in the file  pages/api/auth/register.js .
Right now when we receive this POST request, we invoke  User.create()  to add a new user to the database.
First, I now want to check if the user exists before calling User.create. We use  User.findOne()  to do so. If the user does not exist we create it:
let user = await User.findOne({ where: { email } })
if (!user) {
  user = await User.create({ email, password })
  res.end(JSON.stringify({ status: 'success', message: 'User added' }))
} else {
  res.end(JSON.stringify({ status: 'error', message: 'User already exists' }))
}
Let’s do one more thing: we want to create a session token .
The session is a random string stored in the session_token field.
I first write this function to create a random string of a specific length:
const randomString = (length) => {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let result = ''
  for (let i = length; i > 0; --i) {
    result += chars[Math.floor(Math.random() * chars.length)]
  }
  return result
}
THen I call this to get a token:
const sessionToken = randomString(255)
And I call User.update() to update the user’s data stored in the table, passing this token and a session expiration date set 30 days from now:
const sessionToken = randomString(255)
const d = new Date()
d.setDate(d.getDate() + 30)
User.update(
  {
    session_token: sessionToken,
    session_expiration: d,
  },
  { where: { email } }
)
Here is the full code so far:
import { User, sequelize } from '../../../model.js'
const randomString = (length) => {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let result = ''
  for (let i = length; i > 0; --i) {
    result += chars[Math.floor(Math.random() * chars.length)]
  }
  return result
}
export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  const { email, password, passwordconfirmation } = req.body
  if (password !== passwordconfirmation) {
    res.end(
      JSON.stringify({ status: 'error', message: 'Passwords do not match' })
    )
    return
  }
  let user = await User.findOne({ where: { email } })
  if (!user) {
    user = await User.create({ email, password })
    const sessionToken = randomString(255)
    const d = new Date()
    d.setDate(d.getDate() + 30)
    User.update(
      {
        session_token: sessionToken,
        session_expiration: d,
      },
      { where: { email } }
    )
    res.end(JSON.stringify({ status: 'success', message: 'User added' }))
  } else {
    res.end(JSON.stringify({ status: 'error', message: 'User already exists' }))
  }
}
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/5-3
IMPLEMENT LOGIN
Let’s now implement login!
It’s going to be very similar to the registration, except we call  /api/auth/login , and we don’t have to handle the password confirmation. And contrary to registration, we fail if the user is  not  there yet.
Let’s first install the  cookies  package from npm:
npm install cookies
Now create a  pages/api/auth/login.js  file.
In there, we first receive the  email  and  password  fields from the request, and we see if we can find a user with that email. If not, we return an error message:
import { User, sequelize } from '../../../model.js'
export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  const { email, password } = req.body
  let user = await User.findOne({ where: { email } })
  if (!user) {
    res.end(JSON.stringify({ status: 'error', message: 'User does not exist' }))
    return
  }
}
If the user exists, we check if the password is valid:
const isPasswordValid = await user.isPasswordValid(password)
if (!isPasswordValid) {
  res.end(JSON.stringify({ status: 'error', message: 'Password not valid' }))
  return
}
If the password is valid, we check the session is not expired. If it’s expired, we generate a new session token and a new expiration date. If not, we just expand the expiration date of 30 days:
let sessionToken = null
const sessionExpiration = new Date()
sessionExpiration.setDate(sessionExpiration.getDate() + 30)
if (new Date(user.session_expiration) < new Date()) {
  sessionToken = randomString(255)
  User.update(
    {
      session_token: sessionToken,
      session_expiration: sessionExpiration,
    },
    { where: { email } }
  )
} else {
  sessionToken = user.session_token
  User.update(
    {
      session_expiration: sessionExpiration,
    },
    { where: { email } }
  )
}
Finally we create a cookie and store the session token in there, and we terminate the network request:
const cookies = new Cookies(req, res)
cookies.set('nextbnb_session', sessionToken, {
  httpOnly: true, // true by default
})
res.end(JSON.stringify({ status: 'success', message: 'Logged in' }))
Here’s the full source code of  login.js :
import { User, sequelize } from '../../../model.js'
import Cookies from 'cookies'
const randomString = (length) => {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let result = ''
  for (let i = length; i > 0; --i) {
    result += chars[Math.floor(Math.random() * chars.length)]
  }
  return result
}
export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }
  const { email, password } = req.body
  let user = await User.findOne({ where: { email } })
  if (!user) {
    res.end(JSON.stringify({ status: 'error', message: 'User does not exist' }))
    return
  }
  const isPasswordValid = await user.isPasswordValid(password)
  if (!isPasswordValid) {
    res.end(JSON.stringify({ status: 'error', message: 'Password not valid' }))
    return
  }
  let sessionToken = null
  const sessionExpiration = new Date()
  sessionExpiration.setDate(sessionExpiration.getDate() + 30)
  if (new Date(user.session_expiration) < new Date()) {
    sessionToken = randomString(255)
    User.update(
      {
        session_token: sessionToken,
        session_expiration: sessionExpiration,
      },
      { where: { email } }
    )
  } else {
    sessionToken = user.session_token
    User.update(
      {
        session_expiration: sessionExpiration,
      },
      { where: { email } }
    )
  }
  const cookies = new Cookies(req, res)
  cookies.set('nextbnb_session', sessionToken, {
    httpOnly: true, // true by default
  })
  res.end(JSON.stringify({ status: 'success', message: 'Logged in' }))
}
In the  components/LoginModal.js  file, we are going to to like we did for the registration form and use  useState  to create 2 state variables for the email and the password, and we’re going to use Axios to perform the network request to  /api/auth/login  when the form is submitted:
import { useState } from 'react'
import axios from 'axios'
export default function LoginModal(props) {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const submit = async () => {
    const response = await axios.post('/api/auth/login', {
      email,
      password,
    })
    console.log(response)
    if (response.data.status === 'error') {
      alert(response.data.message)
    }
  }
  return (
    <>
      <h2>Log in</h2>
      <div>
        <form
          onSubmit={(event) => {
            submit()
            event.preventDefault()
          }}
        >
          <input
            id='email'
            type='email'
            placeholder='Email address'
            onChange={(event) => setEmail(event.target.value)}
          />
          <input
            id='password'
            type='password'
            placeholder='Password'
            onChange={(event) => setPassword(event.target.value)}
          />
          <button>Log in</button>
        </form>
      </div>
      <p>
        Don't have an account yet?{' '}
        <a href='javascript:;' onClick={() => props.showSignup()}>
          Sign up
        </a>
      </p>
    </>
  )
}
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/5-4
DETERMINE IF WE ARE LOGGED IN, IN THE CLIENT SIDE
Great! Now we have the session information stored in the  nextbnb_session  cookie
Now I want to get the cookie value from the server on the first page load we do, and we store the login state.
Remember the  store.js  file we created in the previous module?
Add a new store value:
export default createStore({
  login: {
    loggedIn: false,
    setLoggedIn: action((state) => {
      state.loggedIn = true
    }),
  },
  modals: {
    //...
Now we need to add some code to  pages/index.js  and  pages/houses/[id].js , the 2 entry points.
In  pages/index.js :
//at the top
import Cookies from 'cookies'
//at the bottom
export async function getServerSideProps({ req, res, query }) {
  const cookies = new Cookies(req, res)
  const nextbnb_session = cookies.get('nextbnb_session')
  return {
    props: {
      nextbnb_session: nextbnb_session || null,
    },
  }
}
This makes the  nextbnb_session  prop available in the component.
We use  useEffect  now to check if this prop is available, and if so, we are going to call the  setLoggedIn  state action:
//at the top
import { useStoreActions } from 'easy-peasy'
import { useEffect } from 'react'
//...
export default function Home({ nextbnb_session }) {
  const setLoggedIn = useStoreActions((actions) => actions.login.setLoggedIn)
  useEffect(() => {
    if (nextbnb_session) {
      setLoggedIn(true)
    }
  }, [])
  //...
We do the same in  pages/houses/[id].js . Here is the full code of the component:
import Head from 'next/head'
import houses from '../../houses.js'
import Layout from '../../components/Layout'
import DateRangePicker from '../../components/DateRangePicker'
import { useState, useEffect } from 'react'
import { useStoreActions } from 'easy-peasy'
import Cookies from 'cookies'
const calcNumberOfNightsBetweenDates = (startDate, endDate) => {
  const start = new Date(startDate) //clone
  const end = new Date(endDate) //clone
  let dayCount = 0
  while (end > start) {
    dayCount++
    start.setDate(start.getDate() + 1)
  }
  return dayCount
}
export default function House({ house, nextbnb_session }) {
  const [dateChosen, setDateChosen] = useState(false)
  const [numberOfNightsBetweenDates, setNumberOfNightsBetweenDates] = useState(
    0
  )
  const setShowLoginModal = useStoreActions(
    (actions) => actions.modals.setShowLoginModal
  )
  const setLoggedIn = useStoreActions((actions) => actions.login.setLoggedIn)
  useEffect(() => {
    if (nextbnb_session) {
      setLoggedIn(true)
    }
  }, [])
  return (
    <Layout
      content={
        <div className='container'>
          <Head>
            <title>{house.title}</title>
          </Head>
          <article>
            <img src={house.picture} width='100%' alt='House picture' />
            <p>
              {house.type} - {house.town}
            </p>
            <p>{house.title}</p>
          </article>
          <aside>
            <h2>Choose a date</h2>
            <DateRangePicker
              datesChanged={(startDate, endDate) => {
                setNumberOfNightsBetweenDates(
                  calcNumberOfNightsBetweenDates(startDate, endDate)
                )
                setDateChosen(true)
              }}
            />
            {dateChosen && (
              <div>
                <h2>Price per night</h2>
                <p>${house.price}</p>
                <h2>Total price for booking</h2>
                <p>${(numberOfNightsBetweenDates * house.price).toFixed(2)}</p>
                <button
                  className='reserve'
                  onClick={() => {
                    setShowLoginModal()
                  }}
                >
                  Reserve
                </button>{' '}
              </div>
            )}
          </aside>
          <style jsx>{`
            .container {
              display: grid;
              grid-template-columns: 60% 40%;
              grid-gap: 30px;
            }
            aside {
              border: 1px solid #ccc;
              padding: 20px;
            }
          `}</style>
        </div>
      }
    />
  )
}
export async function getServerSideProps({ req, res, query }) {
  const { id } = query
  const cookies = new Cookies(req, res)
  const nextbnb_session = cookies.get('nextbnb_session')
  return {
    props: {
      house: houses.filter((house) => house.id === parseInt(id))[0],
      nextbnb_session: nextbnb_session || null,
    },
  }
}
Now we need to change  components/Header.js  to show a “Logged in” state in the header instead of the links to login and signup.
We first import
import { useStoreState, useStoreActions } from 'easy-peasy'
and inside the component we define
const loggedIn = useStoreState((state) => state.login.loggedIn)
const setLoggedIn = useStoreActions((actions) => actions.login.setLoggedIn)
Finally we add this JSX:
{
  loggedIn ? (
    <nav>
      <ul>
        <li>
          <a>Logged in</a>
        </li>
      </ul>
    </nav>
  ) : (
    <nav>
      <ul>
        <li>
          <a href='#' onClick={() => setShowRegistrationModal()}>
            Sign up
          </a>
        </li>
        <li>
          <a href='#' onClick={() => setShowLoginModal()}>
            Log in
          </a>
        </li>
      </ul>
    </nav>
  )
}
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/5-5












