Back
Chapters

Adding signup feature

Search icon
Search Book
⌘K

Features

Let's implement user sign-up functionality to our application. With this feature, new users can sign-up via UI and they will be able to handle their tasks via Dashboard.

These are the goals we are trying to achieve in this chapter:

  • We need an API for creating users with details such as name, email, and password.

  • We need a new page showing a form having fields: name, email, password, and password confirmation. The form should call the user creation API with the input data upon submission.

    User signup page

  • Users should be able to see a toast message when the form submission is successful.

    Signup Success Toastr

Technical design

To implement user sign-up, we will have to do the following:

  • From the create action in UsersController, we will create an instance of User from the request data and save it to the database.

  • If saving is successful, we will respond with JSON data containing the key called notice. Since we had already implemented the Axios interceptors, it will take care of showing the contents from the notice key in response as a Toastr notification.

  • We will create a new API connector, auth.js for calling sign-up and other authentication-related APIs.

  • We will create a new frontend page Signup for taking user credentials and sending them via API. We will use the /signup route to display that page.

  • To make things more modular, we can accept these credentials from a separate component called Signup inside Form folder.

Adding create action

Open app/controllers/users_controller.rb file and add the following content:

1class UsersController < ApplicationController
2  # previous code
3
4  def create
5    user = User.new(user_params)
6    user.save!
7    respond_with_success("User was successfully created!")
8  end
9
10  private
11
12    def user_params
13      params.require(:user).permit(:name, :email, :password, :password_confirmation)
14    end
15end

Now we need to update routes.rb:

1Rails.application.routes.draw do
2  constraints(lambda { |req| req.format == :json }) do
3    resources :tasks, except: %i[new edit], param: :slug
4    resources :users, only: %i[index create]
5  end
6
7  root "home#index"
8  get "*path", to: "home#index", via: :all
9end

Moving response messages to i18n en.locales

Let's move our response message to locales/en.yml but this time with even more modularization. Since, the response message for User and Task are same, we can combine them using variable interpolation.

To create abstraction, the i18n provides a feature called variable interpolation that allows you to use variables in translation definitions and pass the values for these variables to the translation method.

Let's modify our response messages in en.yml to use variable interpolation:

1en:
2  successfully_created: "%{entity} was successfully created!"
3  successfully_updated: "%{entity} was successfully updated!"
4  task:
5    slug:
6      immutable: "is immutable!"

Now, successfully_created, in en.yml, will expect a variable named, entity. So let's go through our JSON responses and pass that variable with appropriate entity name, wherever we are using successfully_created key.

In users_controller.rb:

1def create
2  user = User.new(user_params)
3  user.save!
4  respond_with_success(t("successfully_created", entity: "User"))
5end

And in tasks_controller.rb:

1def create
2  task = Task.new(task_params)
3  task.save!
4  respond_with_success(t("successfully_created", entity: "Task"))
5end

And hence, by doing this we don't have to write two separate messages.

API connector for authentication

We can create a new API connector, auth.js, which exports all authentication related API calls as functions.

Create a the file app/javascript/src/apis/auth.js and add the following lines to it:

1import axios from "axios";
2
3const signup = payload =>
4  axios.post("/users", {
5    user: payload,
6  });
7
8const authApi = {
9  signup,
10};
11
12export default authApi;

Creating sign up component

We need to create a Signup component to allow users to signup.

We will be keeping all of our auth based components inside Authentication folder. To do so, run the following command:

1mkdir -p app/javascript/src/components/Authentication

Inside the directory, create a new component by running the command:

1touch app/javascript/src/components/Authentication/Signup.jsx

As discussed in the technical design, we will be abstracting signup form logic to a different component called Signup inside Form folder. To create the component that contains the form logic, run the following command:

1mkdir -p app/javascript/src/components/Authentication/Form
2touch app/javascript/src/components/Authentication/Form/Signup.jsx

In Form/Signup.jsx, paste the following contents:

1import React from "react";
2import { Link } from "react-router-dom";
3
4import Input from "components/Input";
5import Button from "components/Button";
6const Signup = ({
7  handleSubmit,
8  setName,
9  setEmail,
10  setPassword,
11  loading,
12  setPasswordConfirmation,
13}) => {
14  return (
15    <div
16      className="flex items-center justify-center min-h-screen px-4
17    py-12 sm:px-6 lg:px-8 bg-gray-50 "
18    >
19      <div className="w-full max-w-md">
20        <h2
21          className="mt-6 text-3xl font-extrabold leading-9
22        text-center text-gray-700"
23        >
24          Sign Up
25        </h2>
26        <div className="text-center">
27          <Link
28            to="/"
29            className="mt-2 text-sm font-medium text-center
30            text-bb-purple transition duration-150 ease-in-out
31            focus:outline-none focus:underline"
32          >
33            Or Login Now
34          </Link>
35        </div>
36        <form className="mt-8" onSubmit={handleSubmit}>
37          <Input
38            label="Name"
39            placeholder="Oliver"
40            onChange={e => setName(e.target.value)}
41          />
42          <Input
43            type="email"
44            label="Email"
45            placeholder="oliver@example.com"
46            onChange={e => setEmail(e.target.value)}
47          />
48          <Input
49            type="password"
50            label="Password"
51            placeholder="********"
52            onChange={e => setPassword(e.target.value)}
53          />
54          <Input
55            type="password"
56            label="Password Confirmation"
57            placeholder="********"
58            onChange={e => setPasswordConfirmation(e.target.value)}
59          />
60          <Button type="submit" buttonText="Register" loading={loading} />
61        </form>
62      </div>
63    </div>
64  );
65};
66
67export default Signup;

Now open Signup.jsx and add the following contents.

1import React, { useState } from "react";
2
3import SignupForm from "components/Authentication/Form/Signup";
4import authApi from "apis/auth";
5
6const Signup = ({ history }) => {
7  const [name, setName] = useState("");
8  const [email, setEmail] = useState("");
9  const [password, setPassword] = useState("");
10  const [passwordConfirmation, setPasswordConfirmation] = useState("");
11  const [loading, setLoading] = useState(false);
12
13  const handleSubmit = async event => {
14    event.preventDefault();
15    setLoading(true);
16    try {
17      await authApi.signup({
18        name,
19        email,
20        password,
21        password_confirmation: passwordConfirmation,
22      });
23      setLoading(false);
24      history.push("/");
25    } catch (error) {
26      logger.error(error);
27      setLoading(false);
28    }
29  };
30  return (
31    <SignupForm
32      setName={setName}
33      setEmail={setEmail}
34      setPassword={setPassword}
35      setPasswordConfirmation={setPasswordConfirmation}
36      loading={loading}
37      handleSubmit={handleSubmit}
38    />
39  );
40};
41
42export default Signup;

We have created a Signup component and we have abstracted form logic to Signup.jsx and form components to Form/Signup.jsx.

Now, we will be adding a route inside of App.jsx that renders our newly created Signup component.

To do so, open App.jsx and add the highlighted lines:

1// ----previous imports if any----
2import Signup from "components/Authentication/Signup";
3
4const App = () => {
5  // previous code if any
6  return (
7    <Router>
8      <Switch>
9        // -----previous code if any-----
10        <Route exact path="/signup" component={Signup} />
11      </Switch>
12    </Router>
13  );
14};

Now let's run Rails server and visit http://localhost:3000/signup and input the details of the new user.

After filling in the details click on submit button and you will be notified whether the request was successful or not by the previously added Toastr component.

In the following chapters we will implement the feature to redirect the user to login page on successful registration.

Now, let's commit the changes:

1git add -A
2git commit -m "Added signup feature"