Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

栏目: IT技术 · 发布时间: 4年前

内容简介:This is the second chapter of our series of creating a simple POS with React, Node, and MongoDB. Today’s tutorial continues from where we left off in the previous chapter: adding register and login functionalities. Today we will add functionalities to hand
Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

This is the second chapter of our series of creating a simple POS with React, Node, and MongoDB. Today’s tutorial continues from where we left off in the previous chapter: adding register and login functionalities. Today we will add functionalities to handle authentication state and log out. We will also create a user profile where users can update user information.

Handle Auth State

Check if User is Logged In

Inside App.js, add this simple function to check the authentication state of the user.

const isLoggedIn = () => {
  return localStorage.getItem('TOKEN_KEY') != null;
};

We need to hide or show the header, sidebar, and footer on the register and login pages depending on whether the user is logged in or not. We use the above function to achieve this.

<Router>
         <Switch>
          <div>
            {isLoggedIn() && (
              <>
                <Header /> <Sidebar />
              </>
            )}
            <Route path="/register" component={Register} />
            <Route path="/login" component={Login} />
            <Route path="/dashboard" component={Dashboard} />
            {isLoggedIn() && <Footer />}
          </div>
        </Switch>
      </Router>

Create a Secured Route

We want to authorize only the logged in users to visit some of the pages in our app. Unauthenticated users must be redirected to the login page when attempted to visit such a page. Following SecuredRoute component will handle this functionality.

const SecuredRoute = ({ component: Component, ...rest }) => (
    
    <Route
      {...rest}
      render={props =>
      
        isLoggedIn() === true ? (
          <Component {...props} />
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );

Now place the dashboard component inside a secured route.

<SecuredRoute path="/dashboard" component={Dashboard} />

When we try to visit the dashboard now, we are redirected to the login page when not logged in.

Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Login page

Prevent Logged In User from Visiting the Login Page

Once users have successfully logged in, they must be prevented from visiting the login page again.

componentDidMount

componentDidMount() {
  if (localStorage.getItem("TOKEN_KEY") != null) {
      return this.props.history.goBack();
   }
 }

Every time a user tries to visit the login page, we check whether the token is present. If yes, we return the user to the last visited page.

Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Last visited page

Implement Logout

When a user logs out, we remove the token stored in the browser and redirect the user to the login page. We show the logout option inside the header menu to the logged in users.

To achieve this, first, remove the existing menu and replace it with the following code. It has additional HTML code to show and handle the logout option inside the dropdown menu.

      <div className="dropdown-menu dropdown-menu-lg dropdown-menu-right">
              <span className="dropdown-item dropdown-header">menu</span>
              <div className="dropdown-divider" />
              <a
                href="#"
                onClick={() => this.Logout()}
                className="dropdown-item"
              >
                <i className="fas fa-sign-out-alt mr-2" /> Logout
              </a>
            </div>

Now we can create the function that handles logging out. The function first prompts the user to confirm logout using a sweet alert. Then, it redirects the user back to the login page.

Import sweetalert and react-router-dom packages to the components/header/header.js file.

import swal from "sweetalert";
import { withRouter} from "react-router-dom";

We use withRouter to use the browser history API inside this file.

Inside the logout function, first, add a button to confirm or cancel the logout request. We use a switch to define button texts and values. If the confirm option is selected, the user is redirected to the login page after removing the token. Otherwise, nothing will be changed.

Logout = () => {
    swal("Are your sure SignOut?", {
      buttons: {
        nope: {
          text: "Let me back",
          value: "nope"
        },
        sure: {
          text: "I'm, Sure",
          value: "sure"
        }
      }
    }).then(value => {
      switch (value) {
        case "sure":
          swal(" SignOut Successfully", "success").then(val => {
            localStorage.removeItem("TOKEN_KEY");
            return this.props.history.push("/login");
          });
          break;
        case "nope":
          swal("Ok", "success");
          break;
        default:
          swal("Got away safely!");
      }
    });
  };

You can see how the logout functionality works in our app.

Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Logout functionality

Update User Profile

In this section, we will implement a profile page that allows updating of user data.

Create a new component named profile. Open the profile.js file.

Frontend

We can reuse the code from register.js and copy the required CSS code from AdminLTE example . Add first name, last name, phone number, and address fields to the profile. In the next chapter, we will provide more options for the user to update.

Add a hidden form to handle the user id to be able to identify the user at form submission.

showForm = ({
    values,
    errors,
    touched,
    handleChange,
    handleSubmit,
    onSubmit,
    isSubmitting,
    setFieldValue
  }) => {
    return (
      <form role="form" onSubmit={handleSubmit}>
        <div className="card-body">
         <input type="hidden" name="id" value={values._id} />
           <div className="form-group has-feedback">
            <label htmlFor="username">Username</label>
            <input
              onChange={handleChange}
              value={values.username}
              type="text"
              className={
                errors.username && touched.username
                  ? "form-control is-invalid"
                  : "form-control"
              }
              id="username"
              placeholder="Enter UserName"
            />
            <label htmlFor="username">First Name</label>
            <input
              onChange={handleChange}
              value={values.first_name}
              type="text"
              className={
                errors.first_name && touched.first_name
                  ? "form-control is-invalid"
                  : "form-control"
              }
              id="first_name"
              placeholder="Enter First Name"
            />
            {errors.first_name && touched.first_name ? (
              <small id="passwordHelp">
                {errors.first_name}
              </small>
            ) : null}
          </div>
          <div className="form-group has-feedback">
            <label htmlFor="last_name">Last Name</label>
            <input
              onChange={handleChange}
              value={values.last_name}
              type="text"
              className={
                errors.last_name && touched.last_name
                  ? "form-control is-invalid"
                  : "form-control"
              }
              id="last_name"
              placeholder="Enter Last Name"
            />
            {errors.last_name && touched.last_name ? (
              <small id="passwordHelp">
                {errors.last_name}
              </small>
            ) : null}
          </div>
          <div className="form-group has-feedback">
            <label htmlFor="phone">phone number</label>
            <input
              onChange={handleChange}
              value={values.phone}
              type="text"
              className={
                errors.phone && touched.phone
                  ? "form-control is-invalid"
                  : "form-control"
              }
              id="phone"
              placeholder="Enter phone number"
            />
            {errors.phone && touched.phone ? (
              <small id="passwordHelp">
                {errors.phone}
              </small>
            ) : null}
          </div>
          <div className="form-group has-feedback">
            <label htmlFor="address">address</label>
            <textarea
              onChange={handleChange}
              value={values.address}
              className={
                errors.address && touched.address
                  ? "form-control is-invalid"
                  : "form-control"
              }
              id="address"
              placeholder="Address"
            />
            {errors.address && touched.address ? (
              <small id="passwordHelp">
                {errors.address}
              </small>
            ) : null}
          </div>
        </div>
        {}
        <div className="card-footer">
          <button
            type="submit"
            disabled={isSubmitting}
            className="btn btn-block btn-primary"
          >
            Save
          </button>
        </div>
      </form>
    );
  };

Update the Yup validation function as the following code shows.

const ProfileSchema = Yup.object().shape({
  username: Yup.string()
    .min(2, "username is Too Short!")
    .max(50, "username is Too Long!")
    .required("username is Required"),
  first_name: Yup.string()
    .min(2, "firstname is Too Short!")
    .max(30, "firstname is Too Long!")
    .required("firstname is Required"),
  last_name: Yup.string()
    .min(2, "lastname is Too Short!")
    .max(30, "lastname is Too Long!")
    .required("lastname is Required"),
  phone: Yup.number("Phone number is use only number")
    .min(10, "Phone number must be 10 characters!")
    .required("Phone number is Required"),
  address: Yup.string()
    .min(12, "address is Too Short!")
    .max(50, "address is Too Long!")
    .required("address is Required"),
  email: Yup.string()
    .email("Invalid email")
    .required("Email is Required")
});

Populate the Form

After loading the form, fill its fields with the stored user data. Here is how we can do this.

Get the User Id from the JWT Token

The only way to identify the logged-in user is the JWT token stored in the local storage. If you can remember, we stored the user id inside the JWT token during token creation. However, this data is encrypted. So we need to decode the data to retrieve the associated user id. Following parseJwt function decodes the token and returns the user id.

parseJwt() {
    let token = localStorage.getItem("TOKEN_KEY");
    var base64Url = token.split(".")[1];
    var base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
    var jsonPayload = decodeURIComponent(
      atob(base64)
        .split("")
        .map(function(c) {
          return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
        })
        .join("")
    );
 
    return JSON.parse(jsonPayload);
  }

Now, create a function that store the user id returned from the above function in the component state.

getData = async id => {
    await axios
      .get("http://localhost:8080/profile/id/" + id)
      .then(response => {
        this.setState({ response: response.data });
      })
      .catch(error => {
        this.setState({ error_message: error.message });
      });
  };

On componentDidMount event, retrieve the user id and get user data.

componentDidMount() {
    let { id } = this.parseJwt();
    this.getData(id);
  }

Attach the state to Formik initialValues variable to populate the form using the data retrieved after loading the page.

                 <Formik
                    enableReinitialize={true}
                    initialValues={
                      result
                        ? result
                        : {
                            id: "",
                            username: "",
                            email: "",
                            first_name: "",
                            last_name: "",
                            phone: "",
                            address: ""
                          }
                    }

Backend

In the backend, add the route that sends user data.

app.get("/profile/id/:id", async (req, res) => { 
   let doc = await Users.findOne({ _id: req.params.id });
   res.json(doc);
});

You can see how the profile page now looks like.

Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Re populate profile page

Handle Form Submission and Avatar

Now we can get back to the task at our hand: uploading an avatar, storing user data, and displaying the user profile.

In the frontend, we add a new form field to the Formik object to add a file using the setFieldValue option.\

let result = this.state.response;<span style={{ color: "#00B0CD", marginLeft: 10 }}>
Add Picture</span>
          <div className="form-group">
            <label htmlFor="exampleInputFile">Avatar upload</label>
            <div className="input-group">
              <div className="custom-file">
                <input
                  type="file"
                  onChange={e => {
                    e.preventDefault();
                    setFieldValue("avatars", e.target.files[0]); 
                     // for upload
                    setFieldValue(
                      "file_obj",
                      URL.createObjectURL(e.target.files[0])
                    ); // for preview image
                  }}
                  name="avatars"
                  className={
                    errors.email && touched.email
                      ? "form-control is-invalid"
                      : "form-control"
                  }
                  accept="image/*"
                  id="avatars"
                  className="custom-file-input"
                  id="exampleInputFile"
                />
                <label className="custom-file-label" htmlFor="exampleInputFile">
                  Choose file
                </label>
              </div>
              <div className="input-group-append">
                <span className="input-group-text" id>
                  Upload
                </span>
              </div>
            </div>
          </div>

When the form is submitted, we will create a new form object and append the form data including the uploaded file.

onSubmit={(values, { setSubmitting }) => {
            let formData = new FormData();
            formData.append("id", values._id);
            formData.append("username", values.username);
            formData.append("first_name", values.first_name);
            formData.append("last_name", values.last_name);
            formData.append("phone", values.phone);
            formData.append("address", values.address);
            formData.append("email", values.email);
              if (values.avatars) {
                  formData.append("avatars", values.avatars);
              }
             
            this.submitForm(formData, this.props.history);
                  setSubmitting(false);
            }}
            validationSchema={ProfileSchema}

We have to update the AJAX request that submits the form to use a PUT request instead of POST.

submitForm = async formData => {
    await axios
      .put("http://localhost:8080/profile", formData)
      .then(res => {
        if (res.data.result === "success") {
          swal("Success!", res.data.message, "success")
        } else if (res.data.result === "error") {
          swal("Error!", res.data.message, "error");
        }
      })
      .catch(error => {
        console.log(error);
        swal("Error!", "Unexpected error", "error");
      });
  };

Backend

Now we have to update the user schema to add the new data fields.

const schema = mongoose.Schema({
  avatars: String,
  username: String,
  email: String,
  first_name: { type: String, default: "" },
  last_name: { type: String, default: "" },
  phone: { type: String, default: "" },
  address: { type: String, default: "" },
  password: String,
  level: { type: String, default: "staff" },
  created: { type: Date, default: Date.now }
});

We have to add a few new packages to handle the submitted data. Formidable handles the form object. Path and fs packages handle file management.

const formidable = require("formidable");
const path = require("path");
const fs = require("fs-extra");
app.use(express.static(__dirname + "/uploaded"));

Also, we have to create a new public directory to store the images.

We add a function to handle the form submission. Formidable can be used to parse form data.

app.put("/profile", async (req, res) => {
  try {
    var form = new formidable.IncomingForm();
    form.parse(req, async (err, fields, files) => {
      let doc = await Users.findOneAndUpdate({ _id: fields.id }, fields);
      await uploadImage(files, fields);
    res.json({ result: "success", message: "Update Successfully" });
    });
  } catch (err) {
    res.json({ result: "error", message: err.errmsg });
  }
});

We need another function to handle file upload.

First, we will rename the avatar image and move it to a directory of our choice. We add the logic to create such a directory if it does not exist. Lastly, we update the user’s data stored in the database.

uploadImage = async (files, doc) => {
  if (files.avatars != null) {
    var fileExtention = files.avatars.name.split(".").pop();
    doc.avatars = `${Date.now()}+${doc.username}.${fileExtention}`;
    var newpath =
      path.resolve(__dirname + "/uploaded/images/") + "/" + doc.avatars;
 
    if (fs.exists(newpath)) {
      await fs.remove(newpath);
    }
    await fs.move(files.avatars.path, newpath);
 
    await Users.findOneAndUpdate({ _id: doc.id }, doc);
  }
};
Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Resulting profile page

The resulting profile page can be seen here.

When a user picks an image for the form, we need to show its preview to the user. When the user reloads the profile page, the uploaded image or a default image must appear on the top.

We will create a new function named showPreviewImage. It listens to the file picker event of the file_obj form field and shows a default image if no image is picked.

showPreviewImage = values => {
    return (
      <div>
        <img
          id="avatars"
          src={
            values.file_obj != null
              ? values.file_obj
              : "http://localhost:8080/images/user.png"
          }
          class="profile-user-img img-fluid img-circle"
          width={100}
        />
      </div>
    );
  };

This is our completed profile page.

Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Completed profile page

When the user visits the profile page after updating the user data, it must show the avatar on the top of the page.

We can easily implement this by updating the src attribute with the avatar attribute of the user object.

getData = async id => {
    await axios
      .get("http://localhost:8080/profile/id/" + id)
      .then(response => {
        document.getElementById("avatars").src = "http://localhost:8080/images/"+response.data.avatars
        this.setState({ response: response.data });
      })
      .catch(error => {
        this.setState({ error_message: error.message });
      });
  };
Create simple POS with React, Node and MongoDB #2: Auth state, Logout, Update Profile

Prevent redirect back to login

Now we will see the following page when we visit the user profile.

Conclusion

In this chapter, we learned how to check the authentication state of a user. We also handled the user logout functionality. We created a new user profile page where a user can update user data and upload an avatar. In the next chapter, we will implement how to send an account activation email and handle account activation. You will learn how to establish an email pipeline where we can communicate with customers to inform about new promotions or send daily reports. Here you can find the GitHub repo for this chapter.

Credit

Protected routes and authentication with React Router v4

Icon made by  Pixel perfect  from  www.flaticon.com

Previous lessons


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Big C++中文版

Big C++中文版

霍斯特曼 / 姚爱红 / 电子工业 / 2007-3 / 85.00元

本书是一本关于C++的优秀教材,南圣何塞州立大学知名教授Horstmann编写。全书深入探讨了C++的知识,并着重强调了安全的标准模板库;本书较厚,但它可用做程序设计专业学生的教材(两学期)。全书在介绍基础知识后,作者论及了一些高级主题。书中面向对象的设计一章探讨了软件开发生命周期问题,给出了实现类关联的实用提示。其他高级主题包括模板,C++标准模板库,设计模式,GUI,关系数据库以及XML等。本......一起来看看 《Big C++中文版》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码