@akasha-platform/vendure-craftjs

A set of hooks and components to provide components that interact with vendure and can be integrated into craftjs.

Usage no npm install needed!

<script type="module">
  import akashaPlatformVendureCraftjs from 'https://cdn.skypack.dev/@akasha-platform/vendure-craftjs';
</script>

README

vendure.craft.js

Overview

@akasha-platform/vendure-craftjs is part of a set of open source repositories which together can be setup to serve a flexible content and ecommerce framework based on microservices/-frontends architecture.

The use of vendure.io as an e-commerce and GraphQL backend and craft.js with a set of customized components has the potential to serve as a solid but flexible interface between merchants/content creators and their customers/target audience.

What is vendure.io?

vendure.io is "A modern, headless GraphQL-based e-commerce framework built with TypeScript & Nodejs"

Its small footprint, easy customisation, extendability and microservice oriented architecture make vendure a perfect candidate for a modern content/e-commerce platform.

What is craft.js?

craft.js is " a React framework for building powerful & feature-rich drag-n-drop page editors."

With craft.js it is possible to fairly quickly build up customised drag and drop page builders as it servers only as a page building framework and lifts off the burden of handling editor state, history and drag and drop handling from you shoulders.

It is possible to build simple React Components with an additional few lines of code to make them compatible with craft.js, however we will see later that the @akasha-platform/craftjs-tool library takes care of a lot of the setup for us.

How do the two work together?

Since both, craft.js and vendure.io do not work together out of the box, a set of repositories was created to make this marriage possible. The microservices are the following:

  • Shop API (Customer facing GraphQL API)
  • Admin API (Admin facing GraphQL API)
  • Admin UI (Admin facing UI)
  • Shopfrontend (Customer facing UI)
  • Page Editor (Admin facing publishing UI)

In order to make this work both, craft.js and vendure.io need to be extended to support the funtionalities required by @akasha-platform/vendure-craftjs.

On the backend side the @akasha-platform/vendure-document-plugin is used to provide an admin facing GraphQL API for managing page trees, meta-data and publishing criteria.

On the frontend side we need to tell craft.js how to fetch page contents and render components using @akasha-platform/craftjs-tools and @akasha-platform/vendure-craftjs.

Setup

This documentation will show you how to setup vendure.io, install all requirements, setup a craft.js page builder and how to extend both the customer facing UI and the admin facing UI (some parts of the vendure admin UI will not be covered).

Project setup

The final project will have the following folder strucure.

example-project/
|-- vendure/
|  |-- package.json
|  |-- ...
|
|-- page-builder/
|  |-- package.json
|  |-- ...
|
|-- shop-front/
|  |-- package.json
|  |-- ...

Installation (Admin facing)

Vendure

Install Vendure
yarn create @vendure vendure

When it asks for the database you're using, select SQLLite for simplicity. If you want to use other database engines you will have to make sure that they are up and reachable for the installation process.

Select Typescript as your programming language and select the rest as you desire.

Configure Vendure
yarn add @akasha-platform/vendure-document-plugin
// vendure-config.ts

import { DocumentPlugin } from "@akasha-platform/vendure-document-plugin";

export const config = {
  /*...*/

  plugins: [/*...*/ DocumentPlugin],
};

and then simply run

yarn migration:generate document-plugin
yarn migration:run document-plugin

This should be enough to have the backend setup. Now start the development environment and check if everything went ok by running

yarn start

If everything went fine, you should be able to login into the shop backend via http://localhost:3000/admin/login

Page Builder

Back in our main project folder we are ready to create our page builder application. You might need to install install-peerdeps for convenience.

Install Page Builder
yarn create react-app page-builder --template typescript
cd ./page-builder
install-peerdeps @akasha-platform/vendure-craftjs

After using install-peerdeps you might want to rearrange some of the packages in the dependencies section of your package.json files to devDependencies and don't forget to re-run yarn install.

Configure Page Builder

In order to actually use the Page Builder we will have to implement a redux store and an apollo client for use by the components delivered with @akasha-platform/vendure-craftjs.

First we will create our redux store:

// ./src/store.ts
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { exportedUserComponentsReducer } from "@akasha-platform/craftjs-tools";
import { navigationReducer } from "@akasha-platform/vendure-craftjs";

const reducer = combineReducers({
  exportedComponents: exportedUserComponentsReducer,
  navigation: navigationReducer,
});

const store = configureStore({
  reducer,
});

export type RootState = ReturnType<typeof store.getState>;

export default store;

And now our clients:

// ./src/client.ts
import {
  ApolloClient,
  ApolloLink,
  createHttpLink,
  from,
  InMemoryCache,
} from "@apollo/client";
import { onError } from "@apollo/client/link/error";

const AUTH_TOKEN = "vendure-auth-token";

const middlewareAuthLink = new ApolloLink((operation, forward) => {
  const token = localStorage.getItem(AUTH_TOKEN);
  const authorizationHeader = token ? `Bearer ${token}` : null;
  operation.setContext({
    headers: {
      authorization: authorizationHeader,
    },
  });
  return forward(operation);
});

const afterwareLink = new ApolloLink((operation, forward) => {
  return forward(operation).map((response) => {
    const context = operation.getContext();
    const {
      response: { headers },
    } = context;

    if (headers) {
      const refreshToken = headers.get("vendure-auth-token");
      if (refreshToken) {
        localStorage.setItem(AUTH_TOKEN, refreshToken);
      }
    }

    return response;
  });
});

const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
    graphQLErrors.map(({ message, locations, path }) =>
      console.log(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
      )
    );

  if (networkError) console.log(`[Network error]: ${networkError}`);
});

export const adminClient = new ApolloClient({
  link: from([
    errorLink,
    middlewareAuthLink,
    afterwareLink,
    createHttpLink({
      uri: "http://localhost:3000/admin-api",
    }),
  ]),
  cache: new InMemoryCache(),
});

export const frontendClient = new ApolloClient({
  link: from([
    errorLink,
    middlewareAuthLink,
    afterwareLink,
    createHttpLink({
      uri: "http://localhost:3000/shop-api",
    }),
  ]),
  cache: new InMemoryCache(),
});

Last but not least we setup our App.tsx to show the craft.js editor. To use a react-redux provider we will have to install it first:

// ./src/App.tsx
import { muiTheme, Resolver } from "@akasha-platform/craftjs-tools";
import {
  AppEditor,
  LoginBox,
  vendureUserComponents,
} from "@akasha-platform/vendure-craftjs";
import { ApolloProvider } from "@apollo/client";
import { Frame } from "@craftjs/core";
import { MuiThemeProvider } from "@material-ui/core";
import React, { useEffect, useState } from "react";
import { Provider } from "react-redux";
import { adminClient, AUTH_TOKEN, frontendClient } from "./client";
import store from "./store";

export const App = () => {
  const resolver = new Resolver({
    ...vendureUserComponents,
  });
  resolver.resolve();

  const [token, setToken] = useState(localStorage.getItem(AUTH_TOKEN));

  useEffect(() => {
    function updateToken() {
      setToken(localStorage.getItem(AUTH_TOKEN));
    }
    window.addEventListener("storage", updateToken);

    return () => {
      window.removeEventListener("storage", updateToken);
    };
  }, [setToken]);

  return (
    <>
      {token ? (
        <AppEditor
          store={store}
          adminClient={adminClient}
          frontendClient={frontendClient}
          resolver={resolver}
          editMode={true}
        >
          <Frame>{resolver.create("ContentArea")}</Frame>
        </AppEditor>
      ) : (
        <Provider store={store}>
          <ApolloProvider client={adminClient}>
            <MuiThemeProvider theme={muiTheme}>
              <LoginBox />
            </MuiThemeProvider>
          </ApolloProvider>
        </Provider>
      )}
    </>
  );
};

export default App;

After running

yarn start

We should be able to start the editor now and directly start to use it.

Installation (Customer facing)

Usage

Features

Content Components

Content Area
Content Editable
Vendure Image

Data driven Components

Product
Collection
Collections
Cart Area
Checkout Area

Form Components

Checkout Form
GraphQL Forms

Pages & Routing

Document Tree and Publishing (+ SEO)
Fetching data based on route properties

Development

Creating Components for use in Editor

"It is just simple React"

Creating Components for GraphQL Data

Products
Collections
Orders/Carts...
Checkout
Search
Custom Components

Wrapping with @akasha-platform/craftjs-tools

Creating settings forms with @akasha-platform/craftjs-tools

Creating actions for use in @akasha-plaform/craftjs-tools's ViewPort

Extending the Editor

Adding Components

Adding Actions

Extending the GraphQL Schema