Airbnb clone, login and signup forms

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.

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 close={() => setShowModal(false)}>test</Modal>
}

we embed the LoginModal or RegistrationModal components depending on the component state:

{
  showModal && (
    <Modal close={() => setShowModal(false)}>
      {showLoginModal && <LoginModal />}
      {showRegistrationModal && <RegistrationModal />}
    </Modal>
  )
}

Remember to also import those components:

import LoginModal from './LoginModal'
import RegistrationModal from './RegistrationModal'

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 the login form:

<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.

See the code on GitHub

Next part: Activate the modal

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

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