Search
⌘K
    to navigateEnterto select Escto close

    Adding signup feature

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

    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    if @user.save
    7      render status: :ok, json: { notice: 'User was successfully created!' }
    8    else
    9      render status: :unprocessable_entity,
    10        json: { error: @user.errors.full_messages.to_sentence }
    11    end
    12  end
    13
    14  private
    15
    16    def user_params
    17      params.require(:user).permit(:name, :email, :password, :password_confirmation)
    18    end
    19end

    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 message in en.yml to use variable interpolation:

    1en:
    2  successfully_created: "%{entity} was successfully created!"

    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  if @user.save
    4    render status: :ok, json: { notice: t('successfully_created', entity: 'User') }
    5  else
    6    render status: :unprocessable_entity,
    7      json: { error: @user.errors.full_messages.to_sentence }
    8  end
    9end

    And in tasks_controller.rb:

    1def create
    2  @task = Task.new(task_params)
    3  authorize @task
    4  if @task.save
    5    render status: :ok, json: { notice: t('successfully_created', entity: 'Task') }
    6  else
    7    errors = @task.errors.full_messages.to_sentence
    8    render status: :unprocessable_entity, json: { error: errors }
    9  end
    10end

    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 => axios.post("/users", payload);
    4
    5const authApi = {
    6  signup
    7};
    8
    9export 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 SignupForm. 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/SignupForm.jsx

    In SignupForm.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 SignupForm = ({
    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 SignupForm;

    Now open Signup.jsx and add the following contents.

    1import React, { useState } from "react";
    2
    3import SignupForm from "components/Authentication/Form/SignupForm";
    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        user: {
    19          name,
    20          email,
    21          password,
    22          password_confirmation: passwordConfirmation
    23        }
    24      });
    25      setLoading(false);
    26      history.push("/");
    27    } catch (error) {
    28      logger.error(error);
    29      setLoading(false);
    30    }
    31  };
    32  return (
    33    <SignupForm
    34      setName={setName}
    35      setEmail={setEmail}
    36      setPassword={setPassword}
    37      setPasswordConfirmation={setPasswordConfirmation}
    38      loading={loading}
    39      handleSubmit={handleSubmit}
    40    />
    41  );
    42};
    43
    44export default Signup;

    We have created a Signup component and we have abstracted form logic to Signup.jsx and form components to SignupForm.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"
    Previous
    Next