filecoin-project / slate

WIP - We're building the place you go to discover, share, and sell files on the web.
https://slate.host
MIT License
526 stars 67 forks source link

Documentation: docs.filecoin.io #27

Closed jimmylee closed 4 years ago

jimmylee commented 4 years ago

Purpose

Deliverable

jimmylee commented 4 years ago

How to build a Filecoin application using Slate React System and Textile's Powergate. (MacOS)

This tutorial is a step by step guide for how to build your own Filecoin application using Textile's Powergate and Slate's React Component Library. After following these steps you will learn how to:

There are some other concepts you will be exposed to as you work through this tutorial:

Set up your environment (MacOS)

First check if MacOS XCode Command Line Tools are installed (You may have done it already):

xcode-select -p

If there is no response, then install them using this command:

xcode-select --install

Then install both node and go

brew install node.
brew install go.

Congrats! Once this finishes you will be able to start your project. Find a directory on your computer. Create a directory named my-powergate-project:

mkdir my-powergate-project

Afterwards create files named package.json and .babelrc.

cd my-powergate-project
touch package.json
touch .babelrc

You will need to add contents to both package.json and .babelrc, for the sake of speed, just copy and paste the contents below:

/package.json

{
  "name": "my-powergate-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "@emotion/core": "^10.0.28",
    "@emotion/cache": "11.0.0-next.12",
    "@emotion/css": "11.0.0-next.12",
    "@emotion/react": "11.0.0-next.12",
    "@emotion/server": "11.0.0-next.12",
    "@emotion/styled": "11.0.0-next.12",
    "@emotion/babel-preset-css-prop": "^10.0.27",
    "next": "latest",
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "@textile/powergate-client": "0.1.0-beta.13",
    "slate-react-system": "0.0.4"
  }
}

/.babelrc

{
  "presets": [
    [
      "next/babel",
      {
        "transform-runtime": {
          "useESModules": false
        }
      }
    ],
    "@emotion/babel-preset-css-prop"
  ]
}

Now run npm install. Once that command has finished, you can create a pages directory and the necessary files for NextJS to function properly:

mkdir pages
cd pages
touch index.js
touch _app.js
touch _document.js

For the sake of time, populate the files with the contents below:

/pages/_document.js

import Document, { Head, Main, NextScript } from "next/document";
import { extractCritical } from "@emotion/server";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    const styles = extractCritical(initialProps.html);
    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          <style
            data-emotion-css={styles.ids.join(" ")}
            dangerouslySetInnerHTML={{ __html: styles.css }}
          />
        </>
      ),
    };
  }

  render() {
    return (
      <html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

/pages/_app.js

import { CacheProvider } from "@emotion/react";
import { cache } from "@emotion/css";

function MyApp({ Component, pageProps }) {
  return (
    <CacheProvider value={cache}>
      <Component {...pageProps} />
    </CacheProvider>
  );
}

export default MyApp;

In your index.js

import * as React from "react";

import { css } from "@emotion/react";

const STYLES_PAGE = css`
  margin: 0;
  padding: 0;
`;

export default class TestPage extends React.Component {
  render() {
    return <div css={STYLES_PAGE}>Hey everyone!</div>;
  }
}

You should be ready to go! If there is any reason you got stuck or it was too difficult, you can go here and start from this example.

Run your web application

In your terminal client, run the following:

next

You should see the following in your terminal screen:

Screen Shot 2020-07-14 at 7 05 00 PM

If you visit localhost:3000 in your browser, it should render a web page.

Import Slate React Components

In /pages/index.js you can import slate-react-system.

import * as React from "react";
import * as System from "slate-react-system";

Once you add slate-react-system. There are a ton of components you can use in your web application! A list of design system components live here: https://slate.host/system.

Lets start with a button:

import * as React from "react";
import * as System from "slate-react-system";

import { css } from "@emotion/react";

const STYLES_PAGE = css`
  margin: 0;
  padding: 0;
`;

export default class TestPage extends React.Component {
  _handleClick = () => alert('You clicked me!');

  render() {
    return (
      <div css={STYLES_PAGE}>
        <System.ButtonPrimary onClick={this._handleClick}>
          Hello There
        </System.ButtonPrimary>
      </div>
    );
  }
}
Screen Shot 2020-07-14 at 7 07 10 PM

Some of these components will require Powergate to function.

Integrate Powergate

Your web application should be running at this point. Now it is time to run Powergate on your machine. You will need to do the following:

git clone https://github.com/textileio/powergate.git
cd powergate
cd docker
make localnet

Once your localnet is running, you can add this code to /pages/index.js.

// NOTE:
// This is how you use the createPow constructor:
// createPow({ host: "http://0.0.0.0:6002" });
import { createPow } from "@textile/powergate-client";

Now we can use slate-react-system components that depend on Powergate running.

Create a Powergate authentication token

Here is how we add a component that can generate an authentication token.

import * as React from "react";
import * as System from "slate-react-system";

import { css } from "@emotion/react";
import { createPow } from "@textile/powergate-client";

const STYLES_PAGE = css`
  margin: 0;
  padding: 0;
`;

export default class TestPage extends React.Component {
  PG = null;
  state = { token: null };

  _handleCreateToken = async () => {
    this.PG = createPow({ host: "http://0.0.0.0:6002" });

    const FFS = await this.PG.ffs.create();

    this.setState({ token: FFS.token ? FFS.token : null });

    this.PG.setToken(FFS.token);
  };

  render() {
    return (
      <div css={STYLES_PAGE}>
        <System.CreateToken onClick={this._handleCreateToken}>
          Hello There
        </System.CreateToken>
      </div>
    );
  }
}

Here is how we add a refresh button and get the updated Powergate state once you have a token.

import * as React from "react";
import * as System from "slate-react-system";

import { css } from "@emotion/react";
import { createPow } from "@textile/powergate-client";

const STYLES_PAGE = css`
  margin: 0;
  padding: 0;
`;

export default class TestPage extends React.Component {
  PG = null;
  state = { token : null, info: null, addrsList: [] };

  _handleCreateToken = async () => {
    this.PG = createPow({ host: "http://0.0.0.0:6002" });

    const FFS = await this.PG.ffs.create();

    this.setState({ token: FFS.token ? FFS.token : null });

    this.PG.setToken(FFS.token);
  };

  _handleRefresh = async () => {
    const { addrsList } = await this.PG.ffs.addrs();
    const { info } = await this.PG.ffs.info();
    this.setState({ addrsList, info });
  }

  render() {
    return (
      <div css={STYLES_PAGE}>
        <System.CreateToken onClick={this._handleCreateToken}>
          Hello There
        </System.CreateToken>
        <System.ButtonPrimary onClick={this._handleRefresh}>
          Refresh
        </System.ButtonPrimary>
      </div>
    );
  }
}

Your screen should look something like this:

Screen Shot 2020-07-14 at 7 08 14 PM

Let's add a component to see a list of Filecoin addresses:

import * as React from "react";
import * as System from "slate-react-system";

import { css } from "@emotion/react";
import { createPow } from "@textile/powergate-client";

const STYLES_PAGE = css`
  margin: 0;
  padding: 0;
`;

export default class TestPage extends React.Component {
  PG = null;
  state = { token : null, info: null, addrsList: [] };

  _handleCreateToken = async () => {
    this.PG = createPow({ host: "http://0.0.0.0:6002" });

    const FFS = await this.PG.ffs.create();

    this.setState({ token: FFS.token ? FFS.token : null });

    this.PG.setToken(FFS.token);
  };

  _handleRefresh = async () => {
    const { addrsList } = await this.PG.ffs.addrs();
    const { info } = await this.PG.ffs.info();
    this.setState({ addrsList, info });
  }

  render() {
    const { token, info } = this.state;

    return (
      <div css={STYLES_PAGE}>
        <System.CreateToken onClick={this._handleCreateToken}>
          Hello There
        </System.CreateToken>

        <System.ButtonPrimary onClick={this._handleRefresh}>
          Refresh
        </System.ButtonPrimary>

        {info ? (
          <System.FilecoinBalancesList data={info.balancesList} />
        ) : null}
      </div>
    );
  }
}

Create a new Filecoin address

Create a class member function for the create address handler.

_handleCreateAddress = async ({ name, type, makeDefault }) => {
  const response = await this.PG.ffs.newAddr(name, type, makeDefault);
}

Add the component to the return value of render()

<System.CreateFilecoinAddress onSubmit={this._handleCreateAddress} />

Now when you hit Refresh you should see a new address after you use the component.

Send funds between Filecoin Addresses

Create a class member function for the send Filecoin address handler.

_handleSendFilecoin = async ({ source, target, amount }) => {
  const response = await this.PG.ffs.sendFil(source, target, amount);
}

Add the component to the return value of render()

<System.SendAddressFilecoin onSubmit={this._handleSendFilecoin} />

Now when you hit Refresh you should see the balances have been updated for Filecoin you are sending between addresses.

Complete example

Here is a complete snippet for your /pages/index.js if you followed all the steps.

import * as React from "react";
import * as System from "slate-react-system";

import { css } from "@emotion/react";
import { createPow } from "@textile/powergate-client";

const STYLES_PAGE = css`
  margin: 0;
  padding: 0;
`;

export default class TestPage extends React.Component {
  PG = null;
  state = { token : null, info: null, addrsList: [] };

  _handleCreateToken = async () => {
    this.PG = createPow({ host: "http://0.0.0.0:6002" });

    const FFS = await this.PG.ffs.create();

    this.setState({ token: FFS.token ? FFS.token : null });

    this.PG.setToken(FFS.token);
  };

  _handleCreateAddress = async ({ name, type, makeDefault }) => {
    const response = await this.PG.ffs.newAddr(name, type, makeDefault);
  }

  _handleSendFilecoin = async ({ source, target, amount }) => {
    const response = await this.PG.ffs.sendFil(source, target, amount);
  }

  _handleRefresh = async () => {
    const { addrsList } = await this.PG.ffs.addrs();
    const { info } = await this.PG.ffs.info();
    this.setState({ addrsList, info });
  }

  render() {
    const { token, info } = this.state;

    return (
      <div css={STYLES_PAGE}>
        <System.CreateToken onClick={this._handleCreateToken}>
          Hello There
        </System.CreateToken>

        <System.ButtonPrimary onClick={this._handleRefresh}>
          Refresh
        </System.ButtonPrimary>

        {info ? (
          <System.FilecoinBalancesList data={info.balancesList} />
        ) : null}

        {info ? (
          <System.CreateFilecoinAddress onSubmit={this._handleCreateAddress} /> 
        ) : null}

        {info ? (
          <System.SendAddressFilecoin onSubmit={this._handleSendFilecoin} /> 
        ) : null}
      </div>
    );
  }
}

That should get your started!

Thank you taking the time to read this! Happy hacking :)

pooja commented 4 years ago

This is awesome @jimmylee ! I think looks good to push it up -- want to do a quick check-in on structure for the docs site?

jimmylee commented 4 years ago

https://github.com/filecoin-project/filecoin-docs/pull/158