Skip to content

Latest commit

 

History

History

ui

Pioneer front-end App

Note

For a more general Pioneer documentation see Dev Readme.

Table of content

Quick Start

After clonning the project, to run a developpment instance of Pioneer simply run:

yarn start

Tech stack

Pioneer is built using React 17. The React development assumes:

Libraries

  • Apollo client - to interact with GraphQL
  • @polkadot/api - to interact with the RPC node
  • RxJS to subscribe to the Polkadot API
  • CKEditor 5 as Markdown editor
  • xstate state management for complex flows in modals
  • Yup validation (partially)
  • date-fns to interact with dates
  • Lodash helper functions
  • React libraries for: routing, pagination, breadcrumbs, dropzone, etc (see package.json)

Build tools

The build scripts uses Webpack directly (no CRA) as it integrates better with custom Webpack extensions (build CKEditor, etc.).

As the Storybook uses Babel a shared Webpack configuration for both webpack and storybook was introduced.

To build the project in a development mode using Webpack dev server:

yarn start

To build a production ready version:

yarn build

Directory structure

The application is divided to 4 types of building blocks (located in the src directory):

  • common - commonly used components & utilities not tied to specific domain
  • A domain/use-case specific:
    • accounts – domain of Polkadot's accounts handling, transferring tokens, balances, etc
    • api – domain containing Joystream's API specific code: hooks, providers, utilities, etc
    • councilcouncil governance
    • forum - forum subsystem
    • membershipsmemberships management
    • working-groupsworking group governance
    • proposalsproposal system
    • bounty, financials, overview: are domains which are not available yet but coming soon.
    • services – Contains internationalization utilities.
  • app – Anything related to the application
    • assets
    • routing
    • pages
    • global providers
  • ProxyApi – [A service calling the Joystream API from a web worker instead of on the main thread])(#ProxyApi)
  • mocks – Utilities and data used for both the deprecated Query-node mocks and the new Storybook mocks.

Some rules/hints:

  • More general packages should not import anything from more specific packages. So code inside common cannot import from accounts or app.
  • Domain specific code can import from other domains and common as you can display account select component when creating membership.
  • The app folder describes how the application is wired-up.

The common and domain specific code is split by functionality:

  • components - Contains React components
  • hooks - Contains react hooks
  • modals - Contains specific modals
  • model - Contains business logic like validation, helpers, etc.
  • queries - Contains domain specific queries
  • types - TypeScript types
  • providers - React context providers

Concepts

Most of the Pioneer uses common React app coding patterns. Some however, requires additional explanation.

Modals

The modals can be created as:

  • locally included component
  • or by requesting to show a global modal using useModal() hook:

The global modals should be included in <GlobalModals /> component. After that can be instantiated from anywhere in the app.

Modals with steps

Some modal's flows require a complex state handling. For those the Pioneer 2 app uses transitions defined using xstate library.

To add a stepper add the proper meta key to the state definition:

const state = {
  id: 'state',
  meta: { isStep: true, stepTitle: 'First Step' }
}

If the states are nested, they will be rendered by @/common/model/machines/getSteps() helper as a nested steps.

Example machine that uses nested steps:

export const myMachine = createMachine<Context, Event, State>({
  initial: 'setup',
  states: {
    setup: {
      id: 'setup',
      meta: { isStep: true, stepTitle: 'Setup' },
      on: { NEXT: 'general' },
    },
    general: {
      id: 'general',
      initial: 'stakingAccount',
      meta: { isStep: true, stepTitle: 'General parameters' },
      states: {
        title: {
          meta: { isStep: true, stepTitle: 'Title' },
          on: {
            BACK: '#setup',
            NEXT: 'description'
          },
        },
        description: {
          meta: { isStep: true, stepTitle: 'Description' },
          on: {
            BACK: 'title',
            NEXT: 'end',
          },
        },
        end: {
          type: 'final',
        },
      },
      onDone: 'transaction',
    },
    tranasction: {}
  }
})

Transactions

For any transaction we use a dedicated machine

img.png

The transaction machine should be used as invoked actor:

// machine.ts
const machine = createMachine({
  states: {
    transaction: {
      invoke: {
        id: 'transaction',
        src: transactionMachine,
        // Automatic transition to the next state
        onDone: [
          {
            target: 'success',
            // Save events to process responses & errors
            actions: assign({ transactionEvents: (context, event) => event.data.events }),
            // Transition guard
            cond: isTransactionSuccess,
          },
          {
            target: 'error',
            actions: assign({ transactionEvents: (context, event) => event.data.events }),
            cond: isTransactionError,
          },
          {
            target: 'canceled',
            cond: isTransactionCanceled,
          },
        ],
      },
    },
    success: { type: 'final' },
    error: { type: 'final' },
    canceled: { type: 'final' },
  }
})

The transaction machine can be used either as a standalone machine or as a child of a bigger flow.

Query Node

To access the archival state of the chain Pioneer fetch such information from the Query node. Read Pioneer architecture section for more details. It is a GraphQL server that allows a convenient API for querying the data.

The following tools are used to consume GraphQL data:

Adding queries

To fetch the data from a GraphQL we use code generated by GraphQL Code Generator

To generate scripts run:

yarn run queries:generate

The queries are organized as below:

  • The query-node schema is stored under @/common/api/schema.graphql
  • GraphQL's queries are stored per every module, inside @/module/queries/ folder - you only need to modify those.
  • The graphq-codegen will generate React hooks for Apollo Client (plugin typescript-react-apollo) that will be exposed as @/module/queries import.

For instance, to query for memberships:

Create a @/memberships/queries/members.graphql file:

query GetMembers {
  memberships {
    id
    handle
  }
}

Then run the yarn run queries:generate script.

Use the generated hook in your code:

import { useGetMembersQuery } from '@/memberships/queries'

const { loading, data } = useGetMembersQuery()

Code generation

Some GraphQL related tools use code generation to scaffold types and react hooks from GraphQL schemas and queries.

After updating any of the *.graphql files run the yarn queries:generated script.

ProxyApi

To interact with the Joystream node a polkadot{.js} API instance as to be created. Because part of the instance creation is resource intensive it results in the App freezing on lower spec machines. In order to solve this issue Pioneer creates the API instance in a web worker and all interaction with the API are done via the web worker.

This is done seamlessly by using on the main thread a ProxyApi which has the same type signature than the ApiRx instance. The ProxyApi forwards the calls to the ApiRx created on the web worker and forward the returned data too.

This feature can be disabled by running Pioneer with DISABLE_PROXY_API=true.

Tips & Tricks

Using well-known accounts with Polkadot-js extension

When a chain is run with in development Substrate creates well-known accounts for Alice, Alice_Stash, Bob, Bob_Stash, Charlie, Dave, Eve and Ferdie. Some of these accounts will are already assigned some token.

To add these to your wallet:

  1. Open extension and click plus sign
  2. Select "Import account from pre-existing seed"
  3. Copy this seed bottom drive obey lake curtain smoke basket hold race lonely fit walk as "existing mnemonic seed"
  4. Open advanced and type the derivation path:
  • For Alice, Bob, Charlie, Dave, Eve & Ferdie use the name as path, e.g. //Eve
  • For Alice_Stash and Bob_Stash use //stash after name, e.g.: //Bob//stash

Using custom Joystream networks

Auto-configure at runtime

Some networks expose JSON configuration describing their different endpoints. These configurations can be used to connect Pioneer to a network with an easy to share URL.

For example if Pioneer is running on localhost:8080 it can connect to the "Atlas dev" playground by going to http://localhost:8080/#/settings?network-config=https://atlas-dev.joystream.org/network/config.json.

A URL such as https://playground.test/config.json can respond with a JSON:

{
  "websocket_rpc": "wss://rpc-endpoint.com:9944",
  "graphql_server": "https://joystream.app/server/graphql",
  "graphql_server_websocket": "wss://joystream.app/server/graphql",
  "member_faucet": "https://joystream.app/member-faucet/register"
}

By visiting pioneer with a query network-config as below:

https://pioneer.joystream.app/#/settings?network-config=https://playground.test/config.json

This will save the endpoints locally under the "Auto-conf" network.

Custom network settings

In case there is the network you wish to connect to has no JSON configuration (or the configuration is incomplete), custom endpoints can be set on the app too. In Settings -> Select Network pick "Custom" there the network endpoints can be defined freely.

Configure at build time

To use custom addresses add the .env file in packages/ui (example: packages/ui/.env.example) and set

  1. REACT_APP_MAINNET_NODE_SOCKET example wss://rpc.joystream.org:9944
  2. REACT_APP_MAINNET_QUERY_NODE example https://query.joystream.org/graphql
  3. REACT_APP_MAINNET_QUERY_NODE_SOCKET example wss://query.joystream.org/graphql
  4. REACT_APP_MAINNET_MEMBERSHIP_FAUCET_URL example https://faucet.joystream.org/member-faucet/register
  5. REACT_APP_MAINNET_BACKEND example https://api-7zai.onrender.com

Please remember to restart the Webpack process after each change.

All the variables are required to be configured for the network to be used.