Browse Source

Tokenomics + Council Reports

Joystream Stats 4 years ago
parent
commit
3a9ac17040

+ 3 - 0
package.json

@@ -12,15 +12,18 @@
     "@types/node-fetch": "^2.5.7",
     "@types/react": "^16.9.53",
     "@types/react-dom": "^16.9.8",
+    "axios": "^0.21.0",
     "bootstrap": "^4.5.3",
     "htmr": "^0.9.2",
     "react": "^17.0.1",
     "react-bootstrap": "^1.4.0",
     "react-dom": "^17.0.1",
     "react-feather": "^2.0.9",
+    "react-markdown": "^5.0.3",
     "react-router": "^5.2.0",
     "react-router-dom": "^5.2.0",
     "react-scripts": "4.0.1",
+    "remark-gfm": "^1.0.0",
     "typescript": "^4.0.3",
     "web-vitals": "^0.2.4"
   },

+ 74 - 2
src/App.tsx

@@ -5,6 +5,7 @@ import { Routes, Loading } from "./components";
 import * as get from "./lib/getters";
 import { domain, wsLocation } from "./config";
 import proposalPosts from "./proposalPosts"; // TODO OPTIMIZE
+import axios from "axios";
 
 // types
 import { Api, Block, IState } from "./types";
@@ -31,9 +32,8 @@ const initialState = {
   proposalCount: 0,
   domain,
   handles: {},
-  lastProposalPost: 0,
-  proposalComments: 0,
   proposalPosts,
+  reports: {},
 };
 
 class App extends React.Component<IProps, IState> {
@@ -88,6 +88,19 @@ class App extends React.Component<IProps, IState> {
     this.fetchProposals(api);
     if (!this.state.validators.length) this.fetchValidators(api);
     if (!this.state.nominators.length) this.fetchNominators(api);
+    //this.fetchTokenomics(api);
+  }
+
+  async fetchStatus() {
+    const { data } = await axios.get("https://status.joystream.org/status");
+    if (!data) return;
+    this.save("tokenomics", data);
+  }
+
+  async fetchTokenomics(api: Api) {
+    const totalIssuance = (await api.query.balances.totalIssuance()).toNumber();
+    const tokenomics = { totalIssuance };
+    this.save("tokenomics", tokenomics);
   }
 
   async fetchCouncil(api: Api) {
@@ -136,6 +149,52 @@ class App extends React.Component<IProps, IState> {
     handles[String(id)] = handle;
     this.save("handles", handles);
   }
+  async fetchReports() {
+    const domain = `https://raw.githubusercontent.com/Joystream/community-repo/master/council-reports`;
+    const apiBase = `https://api.github.com/repos/joystream/community-repo/contents/council-reports`;
+
+    const urls: { [key: string]: string } = {
+      alexandria: `${apiBase}/alexandria-testnet`,
+      archive: `${apiBase}/archived-reports`,
+      template: `${domain}/templates/council_report_template_v1.md`,
+    };
+
+    ["alexandria", "archive"].map((folder) =>
+      this.fetchGithubDir(urls[folder])
+    );
+
+    // template
+    this.fetchGithubFile(urls.template);
+  }
+
+  async saveReport(name: string, content: Promise<string>) {
+    const { reports } = this.state;
+    reports[name] = await content;
+    this.save("reports", reports);
+  }
+
+  async fetchGithubFile(url: string): Promise<string> {
+    const { data } = await axios.get(url);
+    return data;
+  }
+  async fetchGithubDir(url: string) {
+    const { data } = await axios.get(url);
+
+    data.forEach(
+      async (o: {
+        name: string;
+        type: string;
+        url: string;
+        download_url: string;
+      }) => {
+        const match = o.name.match(/^(.+)\.md$/);
+        const name = match ? match[1] : o.name;
+        if (o.type === "file")
+          this.saveReport(name, this.fetchGithubFile(o.download_url));
+        else this.fetchGithubDir(o.url);
+      }
+    );
+  }
 
   loadCouncil() {
     const council = this.load("council");
@@ -162,6 +221,16 @@ class App extends React.Component<IProps, IState> {
     const handles = this.load("handles");
     if (handles) this.setState({ handles });
   }
+  loadReports() {
+    const reports = this.load("reports");
+    if (!reports) return this.fetchReports();
+    //console.log(`loaded reports`, reports);
+    this.setState({ reports });
+  }
+  loadTokenomics() {
+    const tokenomics = this.load("tokenomics");
+    this.setState({ tokenomics });
+  }
   async loadData() {
     await this.loadCouncil();
     await this.loadProposals();
@@ -169,6 +238,8 @@ class App extends React.Component<IProps, IState> {
     await this.loadValidators();
     await this.loadNominators();
     await this.loadHandles();
+    await this.loadTokenomics();
+    await this.loadReports();
     this.setState({ loading: false });
   }
 
@@ -198,6 +269,7 @@ class App extends React.Component<IProps, IState> {
 
   componentDidMount() {
     this.loadData();
+    this.fetchStatus();
     this.initializeSocket();
   }
   componentWillUnmount() {

+ 4 - 0
src/components/Council/index.tsx

@@ -1,4 +1,5 @@
 import React from "react";
+import { Link } from "react-router-dom";
 import User from "../User";
 import { Seat } from "../../types";
 import Loading from "../Loading";
@@ -32,6 +33,9 @@ const Council = (props: {
           </div>
         </div>
       )) || <Loading />}
+      <hr />
+
+      <Link to={`/tokenomics`}>Reports</Link>
     </div>
   );
 };

+ 6 - 1
src/components/Routes/index.tsx

@@ -1,10 +1,15 @@
 import { Switch, Route } from "react-router-dom";
-import { Council, Dashboard, Proposals, Proposal } from "..";
+import { Council, Dashboard, Proposals, Proposal, Tokenomics } from "..";
 import { IState } from "../../types";
 
 const Routes = (props: IState) => {
+  const { reports, tokenomics } = props;
   return (
     <Switch>
+      <Route
+        path="/tokenomics"
+        render={() => <Tokenomics reports={reports} tokenomics={tokenomics} />}
+      />
       <Route
         path="/proposals/:id"
         render={(routeprops) => <Proposal {...routeprops} {...props} />}

+ 33 - 0
src/components/Tokenomics/Overview.tsx

@@ -0,0 +1,33 @@
+import React from "react";
+import { Table } from "react-bootstrap";
+
+import { Tokenomics } from "../../types";
+
+const Overview = (props: Tokenomics) => {
+  const { extecutedBurnsAmount, price, totalIssuance, validators } = props;
+
+  return (
+    <Table>
+      <tbody>
+        <tr>
+          <td>Total Issuance</td>
+          <td>{totalIssuance} JOY</td>
+        </tr>
+        <tr>
+          <td>Burns Amount</td>
+          <td>{extecutedBurnsAmount} JOY</td>
+        </tr>
+        <tr>
+          <td>Validator Stake</td>
+          <td>{validators.total_stake} JOY</td>
+        </tr>
+        <tr>
+          <td>Price</td>
+          <td>{Math.floor(+price * 100000000) / 100} $ / 1 M JOY</td>
+        </tr>
+      </tbody>
+    </Table>
+  );
+};
+
+export default Overview;

+ 53 - 0
src/components/Tokenomics/ReportBrowser.tsx

@@ -0,0 +1,53 @@
+import React from "react";
+import Markdown from "react-markdown";
+import gfm from "remark-gfm";
+
+interface IProps {
+  reports: { [key: string]: string };
+}
+interface IState {
+  selected: string;
+}
+
+const selected = "Alexandria-Council-Leaderboard";
+
+class ReportBrowser extends React.Component<IProps, IState> {
+  constructor(props: IProps) {
+    super(props);
+    this.state = { selected } as IState;
+    this.select = this.select.bind(this);
+  }
+  select(e: { target: { value: string } }) {
+    this.setState({ selected: e.target.value });
+  }
+
+  render() {
+    const { selected } = this.state;
+    const { reports } = this.props;
+    const options = Object.keys(reports).sort();
+
+    return (
+      <div className="h-100 d-flex flex-column">
+        <select
+          name="selected"
+          value={selected}
+          onChange={this.select}
+          className="form-control"
+        >
+          <option>Select a report</option>
+          {options.map((key: string) => (
+            <option key={key}>{key}</option>
+          ))}
+        </select>
+
+        <Markdown
+          plugins={[gfm]}
+          className="mt-1 overflow-auto text-left"
+          children={reports[selected]}
+        />
+      </div>
+    );
+  }
+}
+
+export default ReportBrowser;

+ 10 - 0
src/components/Tokenomics/ReportBrowser.tsx~

@@ -0,0 +1,10 @@
+import React from "react";
+
+const ReportBrowser = props:any => {
+return <div><h2>Council Reports</h2>
+
+
+</div>
+}
+
+export default ReportBrowser

+ 46 - 0
src/components/Tokenomics/index.tsx

@@ -0,0 +1,46 @@
+import React from "react";
+import { Button } from "react-bootstrap";
+import { Link } from "react-router-dom";
+import Overview from "./Overview";
+import ReportBrowser from "./ReportBrowser";
+import Loading from "../Loading";
+
+import { Tokenomics } from "../../types";
+
+const Back = () => {
+  return (
+    <Link to={`/`}>
+      <Button variant="secondary" className="p-1 m-3">
+        back
+      </Button>
+    </Link>
+  );
+};
+
+interface IProps {
+  reports: { [key: string]: string };
+  tokenomics?: Tokenomics;
+}
+
+const CouncilReports = (props: IProps) => {
+  const { reports, tokenomics } = props;
+
+  return (
+    <div className="h-100 py-3 d-flex flex-row justify-content-center">
+      <div className="d-flex flex-column text-right  align-items-right">
+        <div className="box">
+          <h3>Tokenomics</h3>
+          {tokenomics ? <Overview {...tokenomics} /> : <Loading />}
+        </div>
+
+        <Back />
+      </div>
+
+      <div className="box col-8">
+        <ReportBrowser reports={reports} />
+      </div>
+    </div>
+  );
+};
+
+export default CouncilReports;

+ 21 - 0
src/components/Tokenomics/index.tsx~

@@ -0,0 +1,21 @@
+import React from "react";
+import { Button, OverlayTrigger, Tooltip, Table } from "react-bootstrap";
+import { Link } from "react-router-dom";
+import Loading from "..//Loading";
+import moment from "moment";
+
+const Tokenomics = (props: {}) => {
+  return (
+    <div className="bg-light text-center">
+      <Link to={`/`}>
+        <Button variant="secondary" className="p-1 m-3">
+          back
+        </Button>
+      </Link>
+      <h1>Joystream Tokenomics</h1>
+      Previos Councils
+    </div>
+  );
+};
+
+export default Tokenomics;

+ 1 - 0
src/components/index.ts

@@ -7,3 +7,4 @@ export { default as Proposal } from "./Proposals/Proposal"
 export { default as ActiveProposals } from "./Proposals/Active"
 export { default as Loading } from "./Loading";
 export { default as User } from "./User";
+export { default as Tokenomics } from "./Tokenomics";

+ 28 - 1
src/index.css

@@ -24,7 +24,25 @@ a:visited {
 }
 a:active,
 a:hover {
-  color: #fff !important;
+  -color: #fff !important;
+  -background: teal;
+  -padding: 1px;
+}
+
+select {
+  border-color: red;
+}
+
+.select div:hover {
+  background-color: teal !important;
+}
+
+::-moz-selection,
+::selection,
+.select-selected,
+.select-items div:hover {
+  color: white;
+  background-color: teal !important;
 }
 
 h3 {
@@ -38,6 +56,15 @@ h3 {
   background-color: teal;
   text-align: center;
 }
+.box a:hover,
+.box a:active {
+  color: #fff !important;
+  -background: teal;
+}
+
+table td {
+  border-top: none !important;
+}
 
 .title {
   position: fixed;

+ 9 - 1
src/types.ts

@@ -28,8 +28,10 @@ export interface IState {
   threads: Thread[];
   domain: string;
   proposalCount: number;
-  handles: any;
   proposalPosts: any[];
+  handles: { [key: string]: string };
+  tokenomics?: Tokenomics;
+  reports: { [key: string]: string };
   [key: string]: any;
 }
 
@@ -101,3 +103,9 @@ export interface Handles {
 }
 
 export interface Thread {}
+export interface Tokenomics {
+  extecutedBurnsAmount: string;
+  price: string;
+  totalIssuance: string;
+  validators: { total_stake: string };
+}