Drupal
min read
April 23, 2020

Authenticated User Cart with Gatsby and Drupal commerce

Authenticated User Cart with Gatsby and Drupal commerce
Table of contents

Goal

  • Users should be able to register
  • The user should be able to log in as an authenticated user
  • Users should be able to add products to their cart as an authenticated user

Prerequisite

  1. Your Drupal commerce site should be up and going with all the commerce modules enabled that are provided by default.
  2. You should be able to fetch your Drupal data in your Gatsby site.
  3. Also, we will need the commerce cart API module which provides a RESTful interface to interact with our cart in Drupal.

Let’s Get started

  1. Go to the REST option under web services and enable all the cart and user resources with the below permissions.
Gatsby and Drupal commerce

We are done from the Drupal end here. Let’s move to the Gatsby end now.

On Gatsby End

1. Register

The first thing we will do is add user registration functionality.


export const registerUser = async (name, password, email) => {
  const token = await fetch(`${url}rest/session/token?value`);
  const sessionToken = await token.text();
  if (sessionToken) {
    const res = await fetch(`${url}user/register?_format=hal_json`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/hal+json',
        'X-CSRF-TOKEN': sessionToken,
      },
      body: JSON.stringify({
        _links: {
          type: {
            href: `${url}rest/type/user/user`,
          },
        },
        name: { value: name },
        mail: { value: email },
        pass: { value: password },
      }),
    });
    const data = await res.json();
    return data;
  }
};

Create your UserRegistration form and pass all the valid arguments to the registerUser function. Now submit your form to see your user registered on the Drupal end under the People tab. In case you get any permission issues, check under config/people/accounts to see if visitors are allowed to register.

Now that our user is registered. Our next step is to log in.

2. Login

Our log-in functionality is based on the React Context API. So it is necessary you know how the Context API works.

Visit this link and copy four of the below-mentioned files:

  1. drupalOauth.js.
  2. drupalOauthContext.js
  3. withDrupalOauthConsumer.js
  4. withDrupalOauthProvider.js

Place all four files in a single directory named drupal-OAuth. Next, wrap your base component with DrupalOAuthConsumer to initialize the context provider. Your base component will look something like this:


import drupalOauth from '../components/drupal-oauth/drupalOauth';

import withDrupalOauthProvider from '../components/drupal-oauth/withDrupalOauthProvider';

// Initialize a new drupalOauth client which we can use to seed the context provider.

const drupalOauthClient = new drupalOauth({

 drupal_root: 'your drupal root url',

 client_id: 'your simple OAuth consumer Id',

 client_secret: 'Your simple OAuth consumer key',

});
// ... the component definition goes here ...
export default withDrupalOauthProvider(drupalOauthClient, Layout)

Now to create your sign-in or login form take a look at the below code:


import React, {Component} from 'react';
import { FaSpinner } from 'react-icons/fa';


import withDrupalOauthConsumer from '../DrupalOauth/withDrupalOauthConsumer';

class SignIn extends Component {
  constructor(props){
    super(props);
    this.handleSubmit=this.handleSubmit.bind(this);
  }

  state = {
    processing: false,
    username: '',
    password: '',
    error: null,
  };

  handleSubmit = () => {
    event.preventDefault();
    this.setState({ processing: true });
    const { username, password } = this.state;
    if(!username && !password) {
      this.setState({ processing: false });
      this.setState({error: "User name and password doesn't exist"})
    } else {
      this.props.drupalOauthClient.handleLogin(username, password, '').then((res) => {
        localStorage.setItem('username', JSON.stringify(username));
        if(res !==undefined){
          this.setState({ open: false, processing: false });
          this.setState({ error:  <div className="sta-message">'You are now logged in' </div>});
          this.props.updateAuthenticatedUserState(true);
          setTimeout(() => {
            document.location.href="/";
          }, 3000);
        } else {
          this.setState({ processing: false });
          this.setState({error: "User name and password doesn't exist"})
        }
      });   
    } 
  };

  render() {
    const { error, processing } = this.state;

    return (
      <div className="sta-page-wrapper">
        <h3 className="title-28 text-center">Login Now!</h3>
         <div className="form-error" >{error && <p>{error}</p>} </div>
          <form noValidate className="login" id="logIn">
            <div className="form-element">
              <label>username</label>
              <input
                className="form-input"
                name="username"
                type="text"
                placeholder="Username"
                value={this.state.username}
                onChange={event =>
                  this.setState({ [event.target.name]: event.target.value })
                }
              />
              {errors.password && <p className="text-red-500 text-xs italic">{errors.password}</p>}
             </div>
             <div className="form-element">
               <label>Password </label>
               <input
                className="form-input"
                name="password"
                type="password"
                id="passwordSignin"
                value={this.state.password}
                placeholder="Password"
                onChange={event =>
                  this.setState({ [event.target.name]: event.target.value })
                }
              />
             </div>
            {
              processing ?
                FaSpinner
                :
                 <button 
                  onClick={this.handleSubmit}
                  className="button-black"
                  type="submit">
                    Login
                 </button>
            }
           </form>
       </div>
    );
  }
}

export default withDrupalOauthConsumer(SignIn);

export default withDrupalOauthConsumer(SignIn);

When you submit the form Drupal will take care of generating the OAuth token and return it to you. To check this you can wrap your component with DrupalOAuthConsumer, and check via the props.userAuthenticated.

One thing to note here is that the above code does not take into account the user login on the Drupal end. So to be able to log in on the Drupal end add the drupalLogIn code to your drupalOauth.js file and call it inside the fetchOauthToken function. So that every time user tries to log in on the Gatsby end, the user session gets initiated on the Drupal end as well.


/**
   * Login request to Drupal.
   *
   * Exchange username and password.
   * @param username
   * @param password
   * @returns {Promise<void>}
   *   Returns a promise that resolves to JSON response from Drupal.
   */
const drupalLogIn = async (username, password) => {
  const response = await fetch(loginUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: username,
      pass: password,
    }),
  });
  if (response.ok) {
    const json = await response.json();
    if (json.error) {
      throw new Error(json.error.message);
    }
    return json;
  }

Remember we are only taking into account the login functionality here. If you are trying to implement the logout functionality as well, make the below piece of code work same as login.


/**
   * Logout request to Drupal.
   *
   * Logs the user out on drupal end.
   */
const drupalLogout = async () => {
  const oauthToken = await isLoggedIn();
  const logoutoken = oauthToken.access_token;
  if (logoutoken) {
    const res = await fetch(`${process.env.GATSBY_DRUPAL_ROOT}/user/logout?_format=json`, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${logoutoken}`,
      },
    });
    if (res.ok) {
      return true;
    }
  }
};

Also, take into account that drupalOauth.js is a class service. So drupalLogin and drupalLogout functions should be an implementation of a class and need some modifications accordingly.

Authenticated Commerce Cart

Now that our user is logged in and registered, our next step is to post the data to our commerce cart.

If you go through the commerce cart API documentation. It explains how the commerce cart API module works. To post data to the cart as an authenticated user you must be logged in. Once you are logged in. We can POST, GET, and UPDATE our cart. Go through the below code. Which is fairly simple to understand. We are just taking the access token generated by simple OAuth from the Drupal end on login that we have already stored in our browser local storage and sending it as a bearer token as part of our request header to the Drupal end so it can recognize that the user is Authenticated.


import axios from 'axios';
const TokenGenerator = require('uuid-token-generator');
const url = process.env.GATSBY_CART_API_URL;

class CartService {

  getCartToken() {
    const tokgen = new TokenGenerator();
    const oauthToken = JSON.parse(localStorage.getItem('drupal-oauth-token'));
    var myHeaders = new Headers();
    let cartToken = '';
    if(!oauthToken) {
      cartToken = (localStorage.getItem('cartToken') !== null) ? JSON.parse(localStorage.getItem('cartToken')) : tokgen.generate();
      myHeaders.append('Commerce-Cart-Token', cartToken);
      myHeaders.append('Content-Type', 'application/json');
      localStorage.setItem('cartToken', JSON.stringify(cartToken));
    } else {
      cartToken = oauthToken.access_token;
      localStorage.setItem('cartToken', JSON.stringify(cartToken));
      myHeaders.append('Authorization' , `Bearer ${cartToken}`,);
      myHeaders.append('Content-Type', 'application/json',);
    }
    return myHeaders;
  }

  getCartItem = async () => {
    const header = await this.getCartToken();
    const res =  await fetch(`${url}cart?_format=json`,  {
      method: 'GET',
      headers: header
    });
    const cartData = await res.json();
    return cartData;
  } 

  addCartItem = async (id, quantity) => {
    const header = this.getCartToken();
    const res = await fetch(`${url}cart/add?_format=json`, {
      method: 'POST',
      headers: header,
      body: JSON.stringify([{
        purchased_entity_type: 'commerce_product_variation',
        purchased_entity_id: id,
        quantity: quantity
      }])
    })
    const data = await res.json();
    return data;     
  }

  updateCartItem = async (quantity, order_item_id, order_id) => {
    const header = this.getCartToken();
    const res = await fetch(`${url}cart/${order_id}/items/${order_item_id}?_format=json`, {
      method: 'PATCH',
      headers: header,
      body: JSON.stringify({
        "quantity": quantity
      })
      })
    const data = await res.json();
    return data;  
  }

  removeCartItem = async(order_id, order_item_id) => {
    const header = this.getCartToken();
    const res = await fetch(`${url}cart/${order_id}/items/${order_item_id}?_format=json`,{
      method: 'Delete',
      headers: header,
    })
    if (res.status == 204) {
      const data = await this.getCartItem()
      return data;
    }
  }
  
  removeCart = async(order_id) => {
    const header = this.getCartToken();
    const res = await fetch(`${url}cart/${order_id}/items?_format=json`,{
      method: 'Delete',
      headers: header,
    })
    if (res.status == 204) {
      const data = await this.getCartItem()
      return data;
    }
  }

}
const CartHandler = new CartService();
export default CartHandler;

This will allow you to post the cart data as an anonymous user when you are not logged in as well as authenticated user once you are logged in. (Add uuid-token-generator) to your packages to make it work.

To add a product to your cart you can simply import the CartService class into your component and use it as :


import CartHandler from '../Services/CartService';
CartHandler.addCartItem(variationId, quantity);

This is it. Cheers! We are done here. We have been able to successfully register the user, authenticate the user and post data to our commerce cart.

P.S -  If you face any issues. Kindly mention in the comments.

 We are building a decoupled E-commerce site with Gatsby and Drupal commerce. As you are well aware of the fact that in all web applications one of the most important features is user-authenticated browsing. I will not go into the details of why user-authenticated browsing is important as you will find plenty of blog posts on that.This blog post is aimed at users who may find themselves struggling as I did while trying to add the user authentication functionality to a Gatsby site. So let us get started.

Written by
Artwork by
No art workers.
We'd love to talk about your business objectives