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.
-
Users should be able to see a toast message when the form submission is successful.
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 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 => 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"