Browse Source

Implement all of the currently necessary changes to Tokenomics

Edvin Dzidic 4 years ago
parent
commit
c38a8ae7ba

+ 15 - 1
pioneer/packages/apps-routing/src/tokenomics.ts

@@ -12,7 +12,21 @@ export default ([
     display: {
       needsAccounts: true,
       needsApi: [
-        'tx.balances.transfer'
+        'query.balances.totalIssuance',
+        'query.recurringRewards.rewardRelationships',
+        'query.contentWorkingGroup.curatorById',
+        'query.contentWorkingGroup.currentLeadId',
+        'query.council.activeCouncil',
+        'query.council.payoutInterval',
+        'query.council.amountPerPayout',
+        'query.councilElection.newTermDuration',
+        'query.councilElection.votingPeriod',
+        'query.councilElection.revealingPeriod',
+        'query.councilElection.announcingPeriod',
+        'query.storageWorkingGroup.currentLead',
+        'query.storageWorkingGroup.workerById',
+        'query.stake.stakes',
+        'query.staking.currentElected'
       ]
     },
     i18n: {

+ 0 - 25
pioneer/packages/joy-tokenomics/src/Overview/Help.tsx

@@ -1,25 +0,0 @@
-import React, { useState } from 'react';
-import { Icon, Label, Transition } from 'semantic-ui-react';
-
-type HelpProps = {help: string; className?: string; position: { left?: string; right?: string}};
-
-export default function Help (props: HelpProps): React.ReactElement {
-  const [open, setOpen] = useState(false);
-  return (
-    <label
-      className={props.className}
-      onMouseEnter={ (): void => setOpen(true) }
-      onMouseLeave={ (): void => setOpen(false) }
-    >
-      <span style={{ position: 'absolute', display: 'inline-flex', flexWrap: 'wrap', marginTop: '-0.25em' }}>
-        <Icon
-          style={{ margin: '0.25em 0.1em 0.5em 0.25em' }}
-          name="help circle"
-          color="grey"/>
-        <Transition animation="fade" visible={open} duration={500}>
-          <Label basic style={{ minWidth: '15rem', position: 'absolute', ...props.position }} color="grey" content={props.help}/>
-        </Transition>
-      </span>
-    </label>
-  );
-}

+ 29 - 21
pioneer/packages/joy-tokenomics/src/Overview/OverviewTable.tsx

@@ -1,38 +1,46 @@
 import React from 'react';
-import { Table } from 'semantic-ui-react';
-import Help from './Help';
+import { Table, Popup, Icon } from 'semantic-ui-react';
 import styled from 'styled-components';
 
 import { TokenomicsData } from '../lib/getTokenomicsData';
 import { StatusServerData } from './index';
 
-const round = (num: number): number => Math.round((num + Number.EPSILON) * 100) / 100;
-
-const OverviewTableHelp = styled(Help)`
-    position:absolute;
-    cursor:pointer;
-    right:2rem;
-    top:0;
-    @media(max-width: 767px){
-      top:0.8rem;
+const StyledTableRow = styled(Table.Row)`
+  .help-icon{
+    position: absolute !important;
+    right: 0rem !important;
+    top: 0 !important;
+    @media (max-width: 767px){
+      right: 1rem !important;
+      top:0.8rem !important;
     }
+  }
 `;
 
 const OverviewTableRow: React.FC<{item: string; value: string; help?: string}> = ({ item, value, help }) => {
   return (
-    <Table.Row>
+    <StyledTableRow>
       <Table.Cell>
         <div style={{ position: 'relative' }}>
           {item}
-          {help && <OverviewTableHelp position={{ right: '2rem' }} help={help} />}
+          {help &&
+            <Popup
+              trigger={<Icon className='help-icon' name="help circle" color="grey"/>}
+              content={help}
+              position='right center'
+            />}
         </div>
       </Table.Cell>
       <Table.Cell>{value}</Table.Cell>
-    </Table.Row>
+    </StyledTableRow>
   );
 };
 
-const OverviewTable: React.FC<{data: TokenomicsData | undefined; statusData: StatusServerData | undefined | null}> = ({ data, statusData }) => {
+const OverviewTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
+  const displayStatusData = (val?: string, unit?: string): string => (
+    statusData === null ? 'Data currently unavailable...' : statusData ? `${val} ${unit}` : 'Loading...'
+  );
+
   return (
     <Table style={{ marginBottom: '1.5rem' }} celled>
       <Table.Header>
@@ -51,7 +59,7 @@ const OverviewTable: React.FC<{data: TokenomicsData | undefined; statusData: Sta
         <OverviewTableRow
           item='Fiat Pool'
           help='The current value of the Fiat Pool.'
-          value={statusData === null ? 'Data currently unavailable..' : statusData ? `${round(statusData.dollarPool.size)} USD` : 'Loading...'}
+          value={displayStatusData(`${statusData?.dollarPool.size.toFixed(2)}`, 'USD')}
         />
         <OverviewTableRow
           item='Currently Staked Tokens'
@@ -60,12 +68,12 @@ const OverviewTable: React.FC<{data: TokenomicsData | undefined; statusData: Sta
         />
         <OverviewTableRow
           item='Currently Staked Value'
-          value={statusData === null ? 'Data currently unavailable..' : (data && statusData) ? `${round(data.currentlyStakedTokens * Number(statusData.price))} USD` : 'Loading...'}
+          value={statusData === null ? 'Data currently unavailable..' : (data && statusData) ? `${(data.currentlyStakedTokens * Number(statusData.price)).toFixed(2)} USD` : 'Loading...'}
           help='The value of all tokens currently staked for active roles.'
         />
         <OverviewTableRow
           item='Exchange Rate'
-          value={statusData === null ? 'Data currently unavailable..' : statusData ? `${round(Number(statusData.price) * 1000000)} USD/1MJOY` : 'Loading...'}
+          value={displayStatusData(`${(Number(statusData?.price) * 1000000).toFixed(2)}`, 'USD/1MJOY')}
           help='The current exchange rate.'
         />
         {/* <OverviewTableRow help='Sum of all tokens burned through exchanges' item='Total Tokens Burned/Exchanged' value={statusData ? `${statusData.burned} JOY` : 'Loading...'}/> */}
@@ -76,17 +84,17 @@ const OverviewTable: React.FC<{data: TokenomicsData | undefined; statusData: Sta
         />
         <OverviewTableRow
           item='Projected Weekly Token Inflation Rate'
-          value={data ? `${round((data.totalWeeklySpending / data.totalIssuance) * 100)} %` : 'Loading...'}
+          value={data ? `${((data.totalWeeklySpending / data.totalIssuance) * 100).toFixed(2)} %` : 'Loading...'}
           help={'Based on \'Projected Weekly Token Mint Rate\'. Does not include any deflationary forces (fees, slashes, burns, etc.)'}
         />
         <OverviewTableRow
           item='Projected Weekly Value Of Mint'
-          value={statusData === null ? 'Data currently unavailable..' : (data && statusData) ? `${round(data.totalWeeklySpending * Number(statusData.price))} USD` : 'Loading...'}
+          value={statusData === null ? 'Data currently unavailable..' : (data && statusData) ? `${(data.totalWeeklySpending * Number(statusData.price)).toFixed(2)} USD` : 'Loading...'}
           help={'Based on \'Projected Weekly Token Mint Rate\', and current \'Exchange Rate\'.'}
         />
         <OverviewTableRow
           item='Weekly Top Ups'
-          value={statusData === null ? 'Data currently unavailable..' : statusData ? `${statusData.dollarPool.replenishAmount} USD` : 'Loading...'}
+          value={displayStatusData(`${statusData?.dollarPool.replenishAmount}`, 'USD')}
           help={'The current weekly \'Fiat Pool\' replenishment amount. Does not include KPIs, or other potential top ups.'}
         />
       </Table.Body>

+ 0 - 52
pioneer/packages/joy-tokenomics/src/Overview/PieChart.tsx

@@ -1,52 +0,0 @@
-import React, { useEffect } from 'react';
-import styled from 'styled-components';
-import { Icon, Label } from 'semantic-ui-react';
-
-const ChartContainer = styled('div')`
-  display:flex;
-  flex-direction:column;
-  align-items:center;
-`;
-
-const PieChart: React.FC<{icon: any; typeOfChart: string; percentages: Array<{ percent: any; color: string}>}> = ({ icon, typeOfChart, percentages }) => {
-  let cumulativePercent = 0;
-
-  const getCoordinatesForPercent = (percent: number): Array<number> => {
-    const x = Math.cos(2 * Math.PI * percent);
-    const y = Math.sin(2 * Math.PI * percent);
-    return [x, y];
-  };
-
-  useEffect(() => {
-    if (percentages) {
-      const svgEl = document.querySelector(`#pieChart${typeOfChart}`) as Element;
-      percentages.forEach(slice => {
-        const [startX, startY] = getCoordinatesForPercent(cumulativePercent);
-        cumulativePercent += slice.percent;
-        const [endX, endY] = getCoordinatesForPercent(cumulativePercent);
-        const largeArcFlag = slice.percent > 0.5 ? 1 : 0;
-        const pathData = [
-          `M ${startX} ${startY}`,
-          `A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`,
-          'L 0 0'
-        ].join(' ');
-
-        const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
-        pathEl.setAttribute('d', pathData);
-        pathEl.setAttribute('fill', slice.color);
-        svgEl.appendChild(pathEl);
-      });
-    }
-  }, []);
-  return (
-    <ChartContainer>
-      <svg id={`pieChart${typeOfChart}`} viewBox="-1 -1 2 2" style={{ transform: 'rotate(-90deg)', marginBottom: '2rem' }}></svg>
-      <Label as='div'>
-        <Icon name={icon} />
-        <span style={{ fontWeight: 600 }}>{typeOfChart}</span>
-      </Label>
-    </ChartContainer>
-  );
-};
-
-export default PieChart;

+ 66 - 59
pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx

@@ -1,14 +1,25 @@
 import React from 'react';
-import { Table } from 'semantic-ui-react';
+import { Table, Popup, Icon } from 'semantic-ui-react';
 import styled from 'styled-components';
-import Help from './Help';
-import useWindowDimensions from './hooks/useWindowDimensions';
+import { useWindowDimensions } from '../../../joy-utils/src/react/hooks';
 
 import { TokenomicsData } from '../lib/getTokenomicsData';
 import { StatusServerData } from './index';
 
 const round = (num: number): number => Math.round((num + Number.EPSILON) * 100) / 100;
 
+const applyCss = (columns: number[]): string => {
+  let columnString = '';
+  columns.forEach((column, index) => {
+    if (index === 0) {
+      columnString += `td:nth-of-type(${column}), th:nth-of-type(${column})`;
+    } else {
+      columnString += ` ,td:nth-of-type(${column}), th:nth-of-type(${column})`;
+    }
+  });
+  return columnString;
+};
+
 const StyledTable = styled(Table)`
   border: none !important;
   width: 70% !important;
@@ -16,40 +27,30 @@ const StyledTable = styled(Table)`
   @media (max-width: 1400px){
     width:100% !important;
   }
-  & > tbody > tr{
-    td:nth-of-type(1), td:nth-of-type(3), td:nth-of-type(6){
+  & tr {
+    td:nth-of-type(1),
+    th:nth-of-type(1),
+    ${(props): string => applyCss(props.dividecolumnsat)} {
       border-left: 0.12rem solid rgba(20,20,20,0.3) !important;
     }
-    td:nth-of-type(9){
-      border-right: 0.12rem solid rgba(20,20,20,0.3) !important;
-      border-left:0.12rem solid rgba(20,20,20,0.3) !important;
-    }
-  }
-  & > tbody > tr:nth-of-type(6){
-    td{
-      border-bottom: 0.12rem solid rgba(20,20,20,0.3) !important;
-    }
     td:nth-of-type(1){
-      border-bottom-left-radius: .28571429rem !important;
+      position: relative !important;
     }
-    td:nth-of-type(9){
-      border-bottom-right-radius: .28571429rem !important;
+    td:last-child, th:last-child{
+      border-right: 0.12rem solid rgba(20,20,20,0.3) !important;
     }
   }
-  & > thead > tr{
-    th{
-      border-top: 0.12rem solid rgba(20,20,20,0.3) !important;
-    }
-    th:nth-of-type(1), th:nth-of-type(3), th:nth-of-type(6){
-      border-left: 0.12rem solid rgba(20,20,20,0.3) !important;
-    }
-    th:nth-of-type(9){
-      border-right:0.12rem solid rgba(20,20,20,0.3) !important;
-      border-left:0.12rem solid rgba(20,20,20,0.3) !important;
-    }
+  & tr:last-child > td{
+    border-bottom: 0.12rem solid rgba(20,20,20,0.3) !important;
+  }
+  & tr:last-child > td:nth-of-type(1){
+    border-bottom-left-radius: 0.2rem !important;
+  }
+  & tr:last-child > td:last-child{
+    border-bottom-right-radius: 0.2rem !important;
   }
-  & > tbody > tr > td:nth-of-type(1){
-    position:relative !important;
+  th{
+    border-top: 0.12rem solid rgba(20,20,20,0.3) !important;
   }
   & .tableColorBlock{
     height: 1rem;
@@ -61,15 +62,15 @@ const StyledTable = styled(Table)`
   }
 `;
 
-const SpendingAndStakeDistributionTableHelp = styled(Help)`
-    position:absolute;
-    cursor:pointer;
-    right:1.7rem;
-    top:0.7rem;
-    @media(max-width: 767px){
-      top:0.8rem;
-      right:3rem;
+const StyledTableRow = styled(Table.Row)`
+  .help-icon{
+    position: absolute !important;
+    right: 0.5rem !important;
+    top: 0.8rem !important;
+    @media (max-width: 767px){
+      top:0.8rem !important;
     }
+  }
 `;
 
 const SpendingAndStakeTableRow: React.FC<{
@@ -84,8 +85,7 @@ const SpendingAndStakeTableRow: React.FC<{
   color?: string;
   active?: boolean;
   helpContent?: string;
-  helpPosition?: string;
-}> = ({ role, numberOfActors, groupEarning, groupEarningDollar, earningShare, groupStake, groupStakeDollar, stakeShare, color, active, helpContent, helpPosition }) => {
+}> = ({ role, numberOfActors, groupEarning, groupEarningDollar, earningShare, groupStake, groupStakeDollar, stakeShare, color, active, helpContent }) => {
   const parseData = (data: string | undefined): string | JSX.Element => {
     if (data && active) {
       return <em>{data}</em>;
@@ -97,10 +97,14 @@ const SpendingAndStakeTableRow: React.FC<{
   };
 
   return (
-    <Table.Row color={active && 'rgb(150, 150, 150)'}>
+    <StyledTableRow color={active && 'rgb(150, 150, 150)'}>
       <Table.Cell>
         {active ? <strong>{role}</strong> : role}
-        {(helpContent && helpPosition) && <SpendingAndStakeDistributionTableHelp position={helpPosition === 'left' ? { left: '2rem' } : { right: '2rem' }} help={helpContent} />}
+        {helpContent && <Popup
+          trigger={<Icon className='help-icon' name="help circle" color="grey"/>}
+          content={helpContent}
+          position='right center'
+        />}
       </Table.Cell>
       <Table.Cell>{parseData(numberOfActors)}</Table.Cell>
       <Table.Cell>{parseData(groupEarning)}</Table.Cell>
@@ -110,15 +114,23 @@ const SpendingAndStakeTableRow: React.FC<{
       <Table.Cell>{parseData(groupStakeDollar)}</Table.Cell>
       <Table.Cell>{parseData(stakeShare)}</Table.Cell>
       <Table.Cell><div className='tableColorBlock' style={{ backgroundColor: color }}></div></Table.Cell>
-    </Table.Row>
+    </StyledTableRow>
   );
 };
 
 const SpendingAndStakeDistributionTable: React.FC<{data: TokenomicsData | undefined; statusData: StatusServerData | undefined | null}> = ({ data, statusData }) => {
   const { width } = useWindowDimensions();
 
+  const displayStatusData = (group: 'validators' | 'council' | 'storageProviders' | 'storageProviderLead' | 'contentCurators', action: 'rewardsPerWeek' | 'totalStake'): string | undefined => {
+    if (group === 'storageProviderLead') {
+      return statusData === null ? 'Data currently unavailable...' : (data && statusData) && `${(data.storageProviders.lead[action] * Number(statusData.price)).toFixed(2)}`;
+    } else {
+      return statusData === null ? 'Data currently unavailable...' : (data && statusData) && `${(data[group][action] * Number(statusData.price)).toFixed(2)}`;
+    }
+  };
+
   return (
-    <StyledTable celled>
+    <StyledTable dividecolumnsat={[3, 6, 9]} celled>
       <Table.Header>
         <Table.Row>
           <Table.HeaderCell width={4}>Group/Role</Table.HeaderCell>
@@ -137,65 +149,60 @@ const SpendingAndStakeDistributionTable: React.FC<{data: TokenomicsData | undefi
         <SpendingAndStakeTableRow
           role={width <= 1050 ? 'Validators' : 'Validators (Nominators)'}
           helpContent='The current set of active Validators (and Nominators), and the sum of the sets projected rewards and total stakes (including Nominators).'
-          helpPosition={width <= 767 ? 'right' : 'left'}
           numberOfActors={data && `${data.validators.number} (${data.validators.nominators.number})`}
           groupEarning={data && `${Math.round(data.validators.rewardsPerWeek)}`}
-          groupEarningDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.validators.rewardsPerWeek * Number(statusData.price))}`}
+          groupEarningDollar={displayStatusData('validators', 'rewardsPerWeek')}
           earningShare={data && `${round(data.validators.rewardsShare * 100)}`}
           groupStake={data && `${data.validators.totalStake}`}
-          groupStakeDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.validators.totalStake * Number(statusData.price))}`}
+          groupStakeDollar={displayStatusData('validators', 'totalStake')}
           stakeShare={data && `${round(data.validators.stakeShare * 100)}`}
           color='rgb(246, 109, 68)'
         />
         <SpendingAndStakeTableRow
           role={width <= 1015 ? 'Council' : 'Council Members'}
           helpContent='The current Council Members, and the sum of their projected rewards and total stakes (including voters/backers).'
-          helpPosition={width <= 767 ? 'right' : 'left'}
           numberOfActors={data && `${data.council.number}`}
           groupEarning={data && `${Math.round(data.council.rewardsPerWeek)}`}
-          groupEarningDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.council.rewardsPerWeek * Number(statusData.price))}`}
+          groupEarningDollar={displayStatusData('council', 'rewardsPerWeek')}
           earningShare={data && `${round(data.council.rewardsShare * 100)}`}
           groupStake={data && `${data.council.totalStake}`}
-          groupStakeDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.council.totalStake * Number(statusData.price))}`}
+          groupStakeDollar={displayStatusData('council', 'totalStake')}
           stakeShare={data && `${round(data.council.stakeShare * 100)}`}
           color='rgb(254, 174, 101)'
         />
         <SpendingAndStakeTableRow
           role={width <= 1015 ? 'Storage' : 'Storage Providers'}
           helpContent='The current Storage Providers, and the sum of their projected rewards and stakes.'
-          helpPosition={width <= 767 ? 'right' : 'left'}
           numberOfActors={data && `${data.storageProviders.number}`}
           groupEarning={data && `${Math.round(data.storageProviders.rewardsPerWeek)}`}
-          groupEarningDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.storageProviders.rewardsPerWeek * Number(statusData.price))}`}
+          groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek')}
           earningShare={data && `${round(data.storageProviders.rewardsShare * 100)}`}
           groupStake={data && `${data.storageProviders.totalStake}`}
-          groupStakeDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.storageProviders.totalStake * Number(statusData.price))}`}
+          groupStakeDollar={displayStatusData('storageProviders', 'totalStake')}
           stakeShare={data && `${round(data.storageProviders.stakeShare * 100)}`}
           color='rgb(230, 246, 157)'
         />
         <SpendingAndStakeTableRow
           role={width <= 1015 ? 'S. Lead' : width <= 1050 ? 'Storage Lead' : 'Storage Provider Lead'}
           helpContent='Current Storage Provider Lead, and their projected reward and stake.'
-          helpPosition={width <= 767 ? 'right' : 'left'}
           numberOfActors={data && `${data.storageProviders.lead.number}`}
           groupEarning={data && `${Math.round(data.storageProviders.lead.rewardsPerWeek)}`}
-          groupEarningDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.storageProviders.lead.rewardsPerWeek * Number(statusData.price))}`}
+          groupEarningDollar={displayStatusData('storageProviderLead', 'rewardsPerWeek')}
           earningShare={data && `${round(data.storageProviders.lead.rewardsShare * 100)}`}
           groupStake={data && `${data.storageProviders.lead.totalStake}`}
-          groupStakeDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.storageProviders.lead.totalStake * Number(statusData.price))}`}
+          groupStakeDollar={displayStatusData('storageProviderLead', 'totalStake')}
           stakeShare={data && `${round(data.storageProviders.lead.stakeShare * 100)}`}
           color='rgb(170, 222, 167)'
         />
         <SpendingAndStakeTableRow
           role={width <= 1015 ? 'Content' : 'Content Curators'}
           helpContent='The current Content Curators (and their Lead), and the sum of their projected rewards and stakes.'
-          helpPosition={width <= 767 ? 'right' : 'left'}
           numberOfActors={data && `${data.contentCurators.number} (${data.contentCurators.contentCuratorLead})`}
           groupEarning={data && `${Math.round(data.contentCurators.rewardsPerWeek)}`}
-          groupEarningDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.contentCurators.rewardsPerWeek * Number(statusData.price))}`}
+          groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek')}
           earningShare={data && `${round(data.contentCurators.rewardsShare * 100)}`}
           groupStake={data && `${data.contentCurators.totalStake}`}
-          groupStakeDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.contentCurators.totalStake * Number(statusData.price))}`}
+          groupStakeDollar={displayStatusData('contentCurators', 'totalStake')}
           stakeShare={data && `${round(data.contentCurators.stakeShare * 100)}`}
           color='rgb(100, 194, 166)'
         />

+ 77 - 79
pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx

@@ -1,94 +1,92 @@
 import React from 'react';
-import PieChart from './PieChart';
-import { Icon } from 'semantic-ui-react';
+import { Icon, Label } from 'semantic-ui-react';
+import PieChart from '../../../react-components/src/Chart/PieChart';
 import styled from 'styled-components';
 
 import { TokenomicsData } from '../lib/getTokenomicsData';
 
-const TokenomicsChartsContainer = styled('div')`
-  width:30%;
-  display:flex;
-  align-items:center;
-  justify-content:space-evenly;
-  padding: 2rem 0;
-  svg{
-    height:15rem;
-  }
+const StyledPieChart = styled(PieChart)`
+  width:15rem;
+  height:15rem;
+  margin-bottom:1rem;
   @media (max-width: 1650px){
-    svg{
-      height:12rem;
-    }
+    height:12rem;
+    width:12rem;
   }
   @media (max-width: 1400px){
-    width:100%;
-    svg{
-      height:15rem;
-    }
-  }
-  @media (max-width: 550px){
-    flex-direction:column;
-    & > div {
-      margin-bottom: 1.5rem;
-    }
+    height:15rem;
+    width:15rem;
   }
 `;
 
-const TokenomicsCharts: React.FC<{data: TokenomicsData | undefined}> = ({ data }) => {
+const ChartContainer = styled('div')`
+  display:flex;
+  flex-direction:column;
+  align-items:center;
+`;
+
+const TokenomicsCharts: React.FC<{data?: TokenomicsData; className?: string}> = ({ data, className }) => {
   return (
-    <TokenomicsChartsContainer>
-      {data
-        ? <PieChart
-          icon='money'
-          typeOfChart='Spending'
-          percentages={[
-            {
-              percent: data.validators.rewardsShare,
-              color: 'rgb(246, 109, 68)'
-            },
-            {
-              percent: data.council.rewardsShare,
-              color: 'rgb(254, 174, 101)'
-            },
-            {
-              percent: data.storageProviders.rewardsShare,
-              color: 'rgb(230, 246, 157)'
-            },
-            {
-              percent: data.storageProviders.lead.rewardsShare,
-              color: 'rgb(170, 222, 167)'
-            },
-            {
-              percent: data.contentCurators.rewardsShare,
-              color: 'rgb(100, 194, 166)'
-            }
-          ]}/> : <Icon name='circle notched' loading/>}
-      {data
-        ? <PieChart
-          icon='block layout'
-          typeOfChart='Staking'
-          percentages={[
-            {
-              percent: data.validators.stakeShare,
-              color: 'rgb(246, 109, 68)'
-            },
-            {
-              percent: data.council.stakeShare,
-              color: 'rgb(254, 174, 101)'
-            },
-            {
-              percent: data.storageProviders.stakeShare,
-              color: 'rgb(230, 246, 157)'
-            },
-            {
-              percent: data.storageProviders.lead.stakeShare,
-              color: 'rgb(170, 222, 167)'
-            },
-            {
-              percent: data.contentCurators.stakeShare,
-              color: 'rgb(100, 194, 166)'
-            }
-          ]}/> : <Icon name='circle notched' loading />}
-    </TokenomicsChartsContainer>
+    <div className={className}>
+      {data ? <ChartContainer>
+        <StyledPieChart
+          values={[{
+            colors: ['rgb(246, 109, 68)'],
+            label: 'Validators',
+            value: data.validators.rewardsShare * 100
+          }, {
+            colors: ['rgb(254, 174, 101)'],
+            label: 'Council',
+            value: data.council.rewardsShare * 100
+          }, {
+            colors: ['rgb(230, 246, 157)'],
+            label: 'Storage Providers',
+            value: data.storageProviders.rewardsShare * 100
+          }, {
+            colors: ['rgb(170, 222, 167)'],
+            label: 'Storage Lead',
+            value: data.storageProviders.lead.rewardsShare * 100
+          }, {
+            colors: ['rgb(100, 194, 166)'],
+            label: 'Content Curators',
+            value: data.contentCurators.rewardsShare * 100
+          }
+          ]} />
+        <Label as='div'>
+          <Icon name='money' />
+          <span style={{ fontWeight: 600 }}>Spending</span>
+        </Label>
+      </ChartContainer> : <Icon name='circle notched' loading/>}
+      {data ? <ChartContainer>
+        <StyledPieChart
+          values={[{
+            colors: ['rgb(246, 109, 68)'],
+            label: 'Validators',
+            value: data.validators.stakeShare * 100
+          }, {
+            colors: ['rgb(254, 174, 101)'],
+            label: 'Council',
+            value: data.council.stakeShare * 100
+          }, {
+            colors: ['rgb(230, 246, 157)'],
+            label: 'Storage Providers',
+            value: data.storageProviders.stakeShare * 100
+          }, {
+            colors: ['rgb(170, 222, 167)'],
+            label: 'Storage Lead',
+            value: data.storageProviders.lead.stakeShare * 100
+          }, {
+            colors: ['rgb(100, 194, 166)'],
+            label: 'Content Curators',
+            value: data.contentCurators.stakeShare * 100
+          }
+          ]} />
+        <Label as='div'>
+          <Icon name='block layout' />
+          <span style={{ fontWeight: 600 }}>Stake</span>
+        </Label>
+      </ChartContainer> : <Icon name='circle notched' loading/>}
+    </div>
   );
 };
 

+ 26 - 35
pioneer/packages/joy-tokenomics/src/Overview/index.tsx

@@ -1,11 +1,13 @@
-import React, { useEffect, useState } from 'react';
-import getTokenomicsData, { TokenomicsData } from '../lib/getTokenomicsData';
+import React from 'react';
+import getTokenomicsData from '../lib/getTokenomicsData';
 import { api } from '@polkadot/react-api';
 import OverviewTable from './OverviewTable';
 import SpendingAndStakeDistributionTable from './SpendingAndStakeDistributionTable';
 import TokenomicsCharts from './TokenomicsCharts';
 import styled from 'styled-components';
 
+import usePromise from '@polkadot/joy-utils/react/hooks/usePromise';
+
 const SpendingAndStakeContainer = styled('div')`
   display:flex;
   justify-content:space-between;
@@ -19,6 +21,23 @@ const Title = styled('h2')`
   margin: 0 0 2rem 0;
 `;
 
+const StyledTokenomicsCharts = styled(TokenomicsCharts)`
+  width:30%;
+  display:flex;
+  align-items:center;
+  justify-content:space-evenly;
+  padding: 2rem 0;
+  @media (max-width: 1400px){
+    width:100%;
+  }
+  @media (max-width: 550px){
+    flex-direction:column;
+    & > div {
+      margin-bottom: 1.5rem;
+    }
+  }
+`;
+
 export type StatusServerData = {
   dollarPool: {
     size: number;
@@ -28,45 +47,17 @@ export type StatusServerData = {
 }
 
 const Overview: React.FC<{}> = () => {
-  const [data, setData] = useState<TokenomicsData | undefined>();
-  const [statusData, setStatusData] = useState<StatusServerData | undefined | null>();
-
-  const getStatusData = async (): Promise<void> => {
-    try {
-      const response = await fetch('https://status.joystream.org/status');
-      if (response.status >= 200 && response.status <= 299) {
-        const statusData = await response.json();
-        setStatusData(statusData);
-      } else {
-        setStatusData(null);
-      }
-    } catch (e) {
-      console.log(e);
-    }
-  };
-
-  const getApiData = async (): Promise<void> => {
-    try {
-      const data = await getTokenomicsData(api);
-      setData(data);
-    } catch (e) {
-      console.log(e);
-    }
-  };
-
-  useEffect(() => {
-    getApiData();
-    getStatusData();
-  }, []);
+  const [tokenomicsData] = usePromise(() => getTokenomicsData(api), undefined, []);
+  const [statusDataValue, statusDataError] = usePromise(() => fetch('https://status.joystream.org/status').then((res) => res.json()), undefined, []);
 
   return (
         <>
           <Title> Overview </Title>
-          <OverviewTable data={data} statusData={statusData}/>
+          <OverviewTable data={tokenomicsData} statusData={statusDataError ? null : statusDataValue}/>
           <Title> Spending and Stake Distribution </Title>
           <SpendingAndStakeContainer>
-            <SpendingAndStakeDistributionTable data={data} statusData={statusData}/>
-            <TokenomicsCharts data={data} />
+            <SpendingAndStakeDistributionTable data={tokenomicsData} statusData={statusDataError ? null : statusDataValue}/>
+            <StyledTokenomicsCharts data={tokenomicsData} />
           </SpendingAndStakeContainer>
         </>
   );

+ 7 - 4
pioneer/packages/joy-tokenomics/src/lib/getContentCuratorData.ts

@@ -1,5 +1,7 @@
 import { ApiPromise } from '@polkadot/api';
 import { Reward } from './getTokenomicsData';
+import { LeadId } from '@joystream/types/content-working-group';
+import { Option } from '@polkadot/types';
 
 type ContentCurator = {
   reward_relationship: number;
@@ -21,7 +23,7 @@ const getCurators = async (api: ApiPromise): Promise<Array<ContentCurator>> => {
 
 const calculateRewards = (curators: Array<ContentCurator>, recurringRewards: Array<Reward>): number => {
   let rewardsPerBlock = 0;
-  curators.forEach((curator: ContentCurator) => {
+  curators.forEach((curator) => {
     const reward: Reward = recurringRewards.filter((reward: Reward) => reward.recipient === curator.reward_relationship)[0];
     if (reward && reward.amount_per_payout && reward.payout_interval) {
       rewardsPerBlock += reward.amount_per_payout / reward.payout_interval;
@@ -32,7 +34,7 @@ const calculateRewards = (curators: Array<ContentCurator>, recurringRewards: Arr
 
 const calculateStake = async (api: ApiPromise, curators: Array<ContentCurator>): Promise<number> => {
   const stakeIds: Array<number> = []; let totalContentCuratorStake = 0;
-  curators.forEach((curator: any) => {
+  curators.forEach((curator) => {
     if (curator.role_stake_profile) {
       stakeIds.push(curator.role_stake_profile.stake_id);
     }
@@ -44,8 +46,9 @@ const calculateStake = async (api: ApiPromise, curators: Array<ContentCurator>):
   return totalContentCuratorStake;
 };
 
-export default async (api: ApiPromise, recurringRewards: Array<Reward>): Promise<any> => {
-  const currentLead = (await api.query.contentWorkingGroup.currentLeadId()).toJSON() as number;
+export default async (api: ApiPromise, recurringRewards: Array<Reward>) => {
+  const optLeadId = (await api.query.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
+  const currentLead = optLeadId.unwrapOr(null);
   const curators = await getCurators(api);
   const rewardsPerBlock = calculateRewards(curators, recurringRewards);
   const totalContentCuratorStake = await calculateStake(api, curators);

+ 18 - 14
pioneer/packages/joy-tokenomics/src/lib/getCouncilData.ts

@@ -1,29 +1,32 @@
-import { Vec, u32 } from '@polkadot/types';
+import { Vec, Option } from '@polkadot/types';
 import { ApiPromise } from '@polkadot/api';
 import { BlockNumber, BalanceOf } from '@polkadot/types/interfaces';
+import { Seats, Backer } from '@joystream/types/src/council';
 
-const getCouncilMembers = async (api: ApiPromise): Promise<any> => {
+const getCouncilMembers = async (api: ApiPromise) => {
   let totalStake = 0;
-  const activeCouncil = await api.query.council.activeCouncil() as Vec<u32>;
-  const payoutInterval = (await api.query.council.payoutInterval()).toJSON() as number;
+  const activeCouncil = await api.query.council.activeCouncil() as Seats;
+  const payoutInterval = Number((await api.query.council.payoutInterval() as Option<BlockNumber>).unwrapOr(0));
   const amountPerPayout = (await api.query.council.amountPerPayout() as BalanceOf).toNumber();
-  console.log(amountPerPayout, payoutInterval);
-  activeCouncil.map((member: any) => {
+  activeCouncil.map((member) => {
     let stakeAmount = 0;
-    stakeAmount += member.get('stake').toNumber();
-    member.get('backers').forEach((backer: any) => {
-      stakeAmount += backer.stake.toNumber();
-    });
+    stakeAmount += Number(member.get('stake'));
+    const backers = member.get('backers') as Vec<Backer>;
+    if (!backers?.isEmpty) {
+      backers.forEach((backer) => {
+        stakeAmount += Number(backer.get('stake'));
+      });
+    }
     totalStake += stakeAmount;
   });
   return {
     numberOfCouncilMembers: activeCouncil.length,
-    totalCouncilRewardsPerBlock: (amountPerPayout * activeCouncil.length) / payoutInterval,
+    totalCouncilRewardsPerBlock: (amountPerPayout && payoutInterval) ? (amountPerPayout * activeCouncil.length) / payoutInterval : 0,
     totalCouncilStake: totalStake
   };
 };
 
-const calculateCouncilRewards = async (api: ApiPromise, totalCouncilRewardsPerBlock: number): Promise<any> => {
+const calculateCouncilRewards = async (api: ApiPromise, totalCouncilRewardsPerBlock: number): Promise<number> => {
   let weekInBlocks = 100800;
   let councilRewardsInOneWeek = 0;
   const termDuration = (await api.query.councilElection.newTermDuration() as BlockNumber).toNumber();
@@ -57,11 +60,12 @@ const calculateCouncilRewards = async (api: ApiPromise, totalCouncilRewardsPerBl
       return councilRewardsInOneWeek;
     }
   }
+  return councilRewardsInOneWeek;
 };
 
-export default async (api: ApiPromise): Promise<any> => {
+export default async (api: ApiPromise) => {
   const { numberOfCouncilMembers, totalCouncilRewardsPerBlock, totalCouncilStake } = await getCouncilMembers(api);
-  const totalCouncilRewardsInOneWeek = await calculateCouncilRewards(api, totalCouncilRewardsPerBlock) as number;
+  const totalCouncilRewardsInOneWeek = await calculateCouncilRewards(api, totalCouncilRewardsPerBlock);
   return {
     numberOfCouncilMembers,
     totalCouncilRewardsInOneWeek,

+ 0 - 3
pioneer/packages/joy-tokenomics/src/lib/getStorageProviderData.ts

@@ -88,9 +88,6 @@ const calculateProviders = async (api: ApiPromise, storageProviders: Array<Stora
 export default async (api: ApiPromise, recurringRewards: Array<Reward>): Promise<any> => {
   const { numberOfStorageProviders, leadNumber, storageProviders } = await getStorageProviders(api);
   const { totalProviderStake, leadStake, providerRewardsPerBlock, leadRewardsPerBlock } = await calculateProviders(api, storageProviders, recurringRewards);
-
-  console.log(totalProviderStake, leadStake, providerRewardsPerBlock, leadRewardsPerBlock);
-
   return {
     numberOfStorageProviders,
     totalProviderStake,

+ 4 - 4
pioneer/packages/joy-tokenomics/src/lib/getValidatorData.ts

@@ -1,13 +1,13 @@
 import { ApiPromise } from '@polkadot/api';
 import { Vec } from '@polkadot/types';
-import { AccountId, IndividualExposure } from '@polkadot/types/interfaces';
+import { AccountId } from '@polkadot/types/interfaces';
 
-const getValidators = async (api: ApiPromise): Promise<any> => {
+const getValidators = async (api: ApiPromise) => {
   let [totalValidatorStake, slotStake, numberOfNominators, numberOfValidators] = [0, 0, 0, 0];
   const validatorIds = await api.query.staking.currentElected() as Vec<AccountId>;
   await Promise.all(validatorIds.map(async (id: AccountId, index: number) => {
-    const nominators = (await api.derive.staking.info(id)).stakers?.others as Vec<IndividualExposure>;
-    const totalStake = (await api.derive.staking.info(id)).stakers?.total.toNumber() as number;
+    const nominators = (await api.derive.staking.info(id)).stakers?.others.toArray() || [];
+    const totalStake = (await api.derive.staking.info(id)).stakers?.total.toNumber() || 0;
     numberOfValidators += 1;
     totalValidatorStake += totalStake;
     numberOfNominators += nominators.length;

+ 1 - 1
pioneer/packages/joy-tokenomics/src/translate.ts

@@ -4,4 +4,4 @@
 
 import { withTranslation } from 'react-i18next';
 
-export default withTranslation(['app-123code']);
+export default withTranslation(['joy-tokenomics']);

+ 1 - 0
pioneer/packages/joy-utils/src/react/hooks/index.ts

@@ -3,3 +3,4 @@ export { default as useMyMembership } from './useMyMembership';
 export { default as usePromise } from './usePromise';
 export { default as useTransport } from './useTransport';
 export { default as useProposalSubscription } from './proposals/useProposalSubscription';
+export { default as useWindowDimensions } from './useWindowDimensions';

+ 0 - 0
pioneer/packages/joy-tokenomics/src/Overview/hooks/useWindowDimensions.ts → pioneer/packages/joy-utils/src/react/hooks/useWindowDimensions.ts


+ 67 - 0
pioneer/packages/react-components/src/Chart/PieChart.tsx

@@ -0,0 +1,67 @@
+// Copyright 2017-2019 @polkadot/react-components authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+import { BareProps } from '../types';
+
+import BN from 'bn.js';
+import React from 'react';
+import { Pie } from 'react-chartjs-2';
+import { bnToBn } from '@polkadot/util';
+
+interface Value {
+  colors: string[];
+  label: string;
+  value: number | BN;
+}
+
+interface Props extends BareProps {
+  size?: number;
+  values: Value[];
+}
+
+interface Options {
+  colorNormal: string[];
+  colorHover: string[];
+  data: number[];
+  labels: string[];
+}
+
+export default function PieChart ({ className, style, values }: Props): React.ReactElement<Props> {
+  const options: Options = {
+    colorNormal: [],
+    colorHover: [],
+    data: [],
+    labels: []
+  };
+
+  values.forEach(({ colors: [normalColor = '#00f', hoverColor], label, value }): void => {
+    options.colorNormal.push(normalColor);
+    options.colorHover.push(hoverColor || normalColor);
+    options.data.push(bnToBn(value).toNumber());
+    options.labels.push(label);
+  });
+
+  return (
+    <div
+      className={className}
+    >
+      <Pie
+        legend={{
+          display: false
+        }}
+        options={{
+          maintainAspectRatio: false
+        }}
+        data={{
+          labels: options.labels,
+          datasets: [{
+            data: options.data,
+            backgroundColor: options.colorNormal,
+            hoverBackgroundColor: options.colorHover
+          }]
+        }}
+      />
+    </div>
+  );
+}