Ver código fonte

update Calendar Dashboard Forum Proposals Validators

 - drop handles, proposalVotes
Joystream Stats 3 anos atrás
pai
commit
d0c1cba854

+ 2 - 2
src/App.tsx

@@ -34,7 +34,6 @@ const initialState = {
   proposals: [],
   domain,
   members: [],
-  proposalPosts: [],
   providers: [],
   reports: {},
   stakes: {},
@@ -182,7 +181,6 @@ class App extends React.Component<IProps, IState> {
     status.posts = await get.currentPostId(api);
     status.threads = await get.currentThreadId(api);
     status.categories = await get.currentCategoryId(api);
-    //status.channels = await get.currentChannelId(api);
     status.proposalPosts = await api.query.proposalsDiscussion.postCount();
     status.version = version;
     this.save("status", status);
@@ -467,6 +465,7 @@ class App extends React.Component<IProps, IState> {
           toggleFooter={this.toggleFooter}
           toggleStar={this.toggleStar}
           getMember={this.getMember}
+          fetchProposals={this.fetchProposals}
           {...this.state}
         />
 
@@ -521,6 +520,7 @@ class App extends React.Component<IProps, IState> {
     this.toggleFooter = this.toggleFooter.bind(this);
     this.toggleShowStatus = this.toggleShowStatus.bind(this);
     this.getMember = this.getMember.bind(this);
+    this.fetchProposals = this.fetchProposals.bind(this);
   }
 }
 

+ 16 - 16
src/components/Calendar/index.tsx

@@ -16,7 +16,6 @@ import {
 interface IProps {
   proposals: ProposalDetail[];
   status: Status;
-  history: any;
 }
 interface IState {
   items: CalendarItem[];
@@ -38,7 +37,7 @@ class Calendar extends Component<IProps, IState> {
   }
 
   filterItems() {
-    const { status, councils, posts, proposals, threads, handles } = this.props;
+    const { status, councils, posts, proposals, threads } = this.props;
     if (!status?.council) return [];
     const { hide } = this.state;
 
@@ -67,7 +66,7 @@ class Calendar extends Component<IProps, IState> {
       if (hide[group]) return;
       const id = `proposal-${p.id}`;
       const route = `/proposals/${p.id}`;
-      const title = `${p.id} ${p.title} by ${p.author}`;
+      const title = `${p.id} ${p.title} by ${p.author.handle}`;
       const start_time = getTime(p.createdAt);
       const end_time = p.finalizedAt
         ? getTime(p.finalizedAt)
@@ -77,7 +76,7 @@ class Calendar extends Component<IProps, IState> {
 
     // posts
     posts
-      .filter((p) => p.createdAt.block > 1)
+      .filter((p) => p.createdAt > 1)
       .forEach((p) => {
         if (!p) return;
         const group = selectGroup(`Posts`);
@@ -85,33 +84,34 @@ class Calendar extends Component<IProps, IState> {
         const id = `post-${p.id}`;
         const route = `/forum/threads/${p.threadId}`;
         const thread = threads.find((t) => t.id === p.threadId) || {};
-        const handle = handles[p.authorId];
-        const title = `${p.id} in ${thread.title} by ${handle}`;
-        const start_time = getTime(p.createdAt.block);
-        const end_time = getTime(p.createdAt.block);
+        const title = `${p.id} in ${thread.title} by ${p.author.handle}`;
+        const start_time = getTime(p.createdAt);
+        const end_time = getTime(p.createdAt);
         items.push({ id, route, group, title, start_time, end_time });
       });
 
     // councils
+    const d = status.election.durations;
     councils.forEach((c) => {
       const title = `Round ${c.round}`;
       const route = `/councils`;
+
       items.push({
         id: `round-${c.round}`,
         group: 2,
         route,
         title,
-        start_time: getTime(c.start),
-        end_time: getTime(c.end),
+        start_time: getTime(c.start + d[1] + d[2]),
+        end_time: getTime(c.end + d[0] + d[1] + d[2]),
       });
-      return; // TODO set terms on state
+
       items.push({
-        id: `election-round-${round}`,
+        id: `election-round-${c.round}`,
         group: 3,
         route,
         title: `Election ${title}`,
-        start_time: getTime(startBlock),
-        end_time: getTime(beforeTerm + startBlock),
+        start_time: getTime(c.start - d[0]),
+        end_time: getTime(c.start + d[1] + d[2]),
       });
     });
     this.setState({ groups, items });
@@ -129,11 +129,11 @@ class Calendar extends Component<IProps, IState> {
 
   render() {
     const { hide, groups } = this.state;
-    const { status } = this.props;
-
     const items = this.state.items;
     if (!items.length) return <Loading target="items" />;
 
+    const { status } = this.props;
+
     const filters = (
       <div className="d-flex flew-row">
         {groups.map((g) => (

+ 21 - 16
src/components/Dashboard/Forum.tsx

@@ -4,15 +4,14 @@ import Loading from "../Loading";
 
 import { Handles, Post, Thread } from "../../types";
 import {
-  Grid,
-  Paper,
-  Link,
-  makeStyles,
-  Theme,
-  createStyles,
-  Toolbar,
-  AppBar,
-  Typography,
+    Grid,
+    Paper,
+    Link,
+    makeStyles,
+    Theme,
+    createStyles,
+    Toolbar,
+    AppBar, Typography,
 } from "@material-ui/core";
 
 const useStyles = makeStyles((theme: Theme) =>
@@ -30,8 +29,12 @@ const useStyles = makeStyles((theme: Theme) =>
   })
 );
 
-const Forum = (props: { posts: Post[]; threads: Thread[] }) => {
-  const { posts, threads, startTime } = props;
+const Forum = (props: {
+  handles: Handles;
+  posts: Post[];
+  threads: Thread[];
+}) => {
+  const { handles, posts, threads, startTime } = props;
   const classes = useStyles();
   if (!posts.length) return <Loading target="posts" />;
   return (
@@ -52,11 +55,11 @@ const Forum = (props: { posts: Post[]; threads: Thread[] }) => {
       >
         <AppBar className={classes.root} position="static">
           <Toolbar>
-            <Typography variant="h5" className={classes.title}>
-              <Link style={{ color: "#fff" }} href={"/forum"}>
-                Forum
-              </Link>
-            </Typography>
+              <Typography variant="h5" className={classes.title}>
+                  <Link style={{ color: "#fff" }} href={"/forum"}>
+                    Forum
+                  </Link>
+              </Typography>
           </Toolbar>
         </AppBar>
 
@@ -67,7 +70,9 @@ const Forum = (props: { posts: Post[]; threads: Thread[] }) => {
             <LatestPost
               key={post.id}
               selectThread={() => {}}
+              handles={handles}
               post={post}
+              thread={threads.find((t) => t.id === post.threadId)}
               startTime={startTime}
             />
           ))}

+ 0 - 41
src/components/Dashboard/Nominators.tsx

@@ -1,41 +0,0 @@
-import React from "react";
-import User from "../User";
-import { Handles } from "../../types";
-import Loading from "../Loading";
-
-const Nominators = (props: { nominators: string[]; handles: Handles }) => {
-  const { nominators, handles } = props;
-
-  const half = Math.floor(nominators.length / 2) + 1;
-
-  return (
-    <div className="box col md-5 sm-10">
-      <h3>Nominators</h3>
-
-      {(nominators.length && (
-        <div className="d-flex flex-row">
-          <div className="col">
-            {nominators.slice(0, half).map((nominator: string) => (
-              <User
-                key={nominator}
-                id={nominator}
-                handle={handles[nominator]}
-              />
-            ))}
-          </div>
-          <div className="col">
-            {nominators.slice(half).map((nominator: string) => (
-              <User
-                key={nominator}
-                id={nominator}
-                handle={handles[nominator]}
-              />
-            ))}
-          </div>
-        </div>
-      )) || <Loading />}
-    </div>
-  );
-};
-
-export default Nominators;

+ 23 - 21
src/components/Dashboard/Proposals.tsx

@@ -1,6 +1,5 @@
 import React from "react";
-import ProposalsTable from "../Proposals/ProposalTable";
-import Loading from "../Loading";
+import { ProposalTable } from "..";
 import {
   AppBar,
   createStyles,
@@ -12,6 +11,7 @@ import {
   Toolbar,
   Typography,
 } from "@material-ui/core";
+import { Council, Member, ProposalDetail, Post } from "../../types";
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -27,27 +27,29 @@ const useStyles = makeStyles((theme: Theme) =>
 );
 
 const Proposals = (props: {
-  proposals;
-  validators;
-  councils;
-  members;
-  posts;
+  proposals: ProposalDetail[];
+  validators: string[];
+  councils: Council[];
+  members: Member[];
+  posts: Post[];
   startTime: number;
   block: number;
+  status: { council: Council };
 }) => {
-  const { proposals, validators, councils, members, posts, startTime, block } =
-    props;
+  const {
+    proposals,
+    validators,
+    councils,
+    members,
+    posts,
+    block,
+    status,
+  } = props;
   const classes = useStyles();
   const pending = proposals.filter((p) => p && p.result === "Pending");
-  if (!proposals.length) return <Loading target="proposals" />;
-  if (!pending.length) {
-    const loading = proposals.filter((p) => !p);
-    return loading.length ? (
-      <Loading />
-    ) : (
-      <div className="box">No active proposals.</div>
-    );
-  }
+
+  if (proposals.length && !pending.length)
+    return <div className="box">No active proposals.</div>;
 
   return (
     <Grid
@@ -84,15 +86,15 @@ const Proposals = (props: {
             </Link>
           </Toolbar>
         </AppBar>
-        <ProposalsTable
+        <ProposalTable
           block={block}
           hideNav={true}
           proposals={pending}
-          proposalPosts={props.proposalPosts}
           members={members}
+          council={status.council}
           councils={councils}
           posts={posts}
-          startTime={startTime}
+          status={status}
           validators={validators}
         />
       </Paper>

+ 0 - 53
src/components/Dashboard/Validators.tsx

@@ -1,53 +0,0 @@
-import React from "react";
-import User from "../User";
-import { Handles } from "../../types";
-import Loading from "../Loading";
-
-const Validators = (props: {
-  validators: string[];
-  members: { rootKey: string }[];
-}) => {
-  const { getMember, validators } = props;
-
-  const third = Math.floor(validators.length / 3) + 1;
-
-  return (
-    <div className="box col md-5 sm-10">
-      <h3>Validators</h3>
-
-      {(validators.length && (
-        <div className="d-flex flex-row">
-          <div className="col">
-            {validators.slice(0, third).map((validator: string) => (
-              <User
-                key={validator}
-                id={validator}
-                handle={getMember(validator)?.handle || validator}
-              />
-            ))}
-          </div>
-          <div className="col">
-            {validators.slice(third, third * 2).map((validator: string) => (
-              <User
-                key={validator}
-                id={validator}
-                handle={getMember(validator)?.handle || validator}
-              />
-            ))}
-          </div>
-          <div className="col">
-            {validators.slice(third * 2).map((validator: string) => (
-              <User
-                key={validator}
-                id={validator}
-                handle={getMember(validator)?.handle || validator}
-              />
-            ))}
-          </div>
-        </div>
-      )) || <Loading target="Validators" />}
-    </div>
-  );
-};
-
-export default Validators;

+ 1 - 3
src/components/Dashboard/index.tsx

@@ -16,7 +16,6 @@ const Dashboard = (props: IProps) => {
     getMember,
     toggleStar,
     councils,
-    handles,
     members,
     nominators,
     posts,
@@ -55,7 +54,7 @@ const Dashboard = (props: IProps) => {
             proposals={proposals}
             proposalPosts={props.proposalPosts}
             validators={validators}
-            startTime={status.startTime}
+            status={status}
           />
           <Forum posts={posts} threads={threads} startTime={status.startTime} />
           <Grid
@@ -70,7 +69,6 @@ const Dashboard = (props: IProps) => {
             <Validators
               toggleStar={toggleStar}
               councils={councils}
-              handles={handles}
               members={members}
               posts={posts}
               proposals={proposals}

+ 1 - 5
src/components/Forum/Content.tsx

@@ -4,10 +4,9 @@ import Threads from "./Threads";
 import Posts from "./Posts";
 import LatestPost from "./LatestPost";
 
-import { Handles, Category, Thread, Post } from "../../types";
+import { Category, Thread, Post } from "../../types";
 
 const Content = (props: {
-  handles: Handles;
   categories: Category[];
   category?: Category;
   thread?: Thread;
@@ -29,7 +28,6 @@ const Content = (props: {
     threads,
     thread,
     posts,
-    handles,
     startTime,
     latest,
     filterPosts,
@@ -44,7 +42,6 @@ const Content = (props: {
           posts.filter((p) => p.threadId === thread.id),
           props.searchTerm
         )}
-        handles={handles}
         startTime={startTime}
       />
     );
@@ -79,7 +76,6 @@ const Content = (props: {
                 key={p.id}
                 selectThread={selectThread}
                 startTime={startTime}
-                handles={handles}
                 thread={threads.find((t) => t.id === p.threadId)}
                 post={p}
               />

+ 1 - 21
src/components/Forum/NavBar.tsx

@@ -1,11 +1,8 @@
 import React from "react";
 import { Form, Nav, Navbar } from "react-bootstrap";
-import { Link } from "react-router-dom";
 
 import { ChevronRight } from "react-feather";
-import { Category, Post, Thread } from "../../types";
-
-import Missing from "./Missing";
+import { Category, Thread } from "../../types";
 
 const CategoryNav = (props: {
   selectThread: (id: number) => void;
@@ -34,21 +31,14 @@ const NavBar = (props: {
   selectCategory: (id: number) => void;
   selectThread: (id: number) => void;
   getMinimal: (array: { id: number }[]) => any;
-  categories: Category[];
   category?: Category;
-  threads: Thread[];
   thread?: Thread;
-  posts: Post[];
   searchTerm: string;
   handleChange: (e: any) => void;
 }) => {
   const {
     selectCategory,
     selectThread,
-    getMinimal,
-    categories,
-    threads,
-    posts,
     category,
     thread,
     searchTerm,
@@ -57,9 +47,6 @@ const NavBar = (props: {
 
   return (
     <Navbar bg="dark" variant="dark">
-      <Navbar.Brand>
-        <Link to="/">Joystream</Link>
-      </Navbar.Brand>
       <Navbar.Brand
         onClick={() => {
           selectCategory(0);
@@ -72,13 +59,6 @@ const NavBar = (props: {
       <ThreadNav thread={thread} />
 
       <Nav className="ml-auto">
-        <Missing
-          getMinimal={getMinimal}
-          categories={categories}
-          threads={threads}
-          posts={posts}
-        />
-
         <Form>
           <input
             type="search"

+ 6 - 7
src/components/Forum/PostBox.tsx

@@ -1,6 +1,5 @@
 import React from "react";
 import User from "../User";
-import { Handles } from "../../types";
 import { domain } from "../../config";
 import moment from "moment";
 import Markdown from "react-markdown";
@@ -8,24 +7,24 @@ import gfm from "remark-gfm";
 
 const PostBox = (props: {
   startTime: number;
-  handles: Handles;
   id: number;
   authorId: string;
   createdAt: { block: number; time: number };
   text: string;
   threadId: number;
 }) => {
-  const { createdAt, startTime, id, authorId, handles, threadId, text } = props;
-  const created = moment(startTime + createdAt.block * 6000).fromNow();
-
+  const { createdAt, startTime, id, authorId, handle, threadId, text } = props;
+  const created = moment(startTime + createdAt * 6000);
   return (
     <div className="box" key={id}>
       <div>
         <div className="float-right">
           <a href={`${domain}/#/forum/threads/${threadId}`}>reply</a>
         </div>
-        <div className="float-left">{created}</div>
-        <User key={authorId} id={authorId} handle={handles[authorId]} />
+        <div className="float-left">
+          {created.isValid() ? created.fromNow() : <span />}
+        </div>
+        <User key={authorId} id={authorId} handle={handle} />
       </div>
       <Markdown
         plugins={[gfm]}

+ 3 - 5
src/components/Forum/Posts.tsx

@@ -1,14 +1,13 @@
 import React from "react";
-import { Handles, Thread, Post } from "../../types";
+import { Thread, Post } from "../../types";
 import PostBox from "./PostBox";
 
 const Posts = (props: {
   startTime: number;
-  handles: Handles;
   thread?: Thread;
   posts: Post[];
 }) => {
-  const { handles, startTime, posts } = props;
+  const { startTime, posts } = props;
 
   return (
     <div className="overflow-auto" style={{ height: `90%` }}>
@@ -16,8 +15,7 @@ const Posts = (props: {
         .sort((a, b) => b.createdAt.block - a.createdAt.block)
         .map((post) => (
           <PostBox
-            key={post.id}
-            handles={handles}
+            key={post.id}         
             startTime={startTime}
             {...post}
           />

+ 3 - 5
src/components/Forum/index.tsx

@@ -1,5 +1,5 @@
 import React from "react";
-import { Handles, Category, Post, Thread } from "../../types";
+import { Category, Post, Thread } from "../../types";
 import NavBar from "./NavBar";
 import Content from "./Content";
 
@@ -9,7 +9,6 @@ interface IProps {
   posts: Post[];
   categories: Category[];
   threads: Thread[];
-  handles: Handles;
   now: number;
 }
 interface IState {
@@ -61,7 +60,7 @@ class Forum extends React.Component<IProps, IState> {
     return s === ""
       ? posts
       : posts.filter((p) => {
-          const handle = this.props.handles[p.authorId] || "";
+          const handle = p.author.handle;
           const text = p.text.toLowerCase();
           return text.includes(s) || handle.includes(s);
         });
@@ -93,7 +92,7 @@ class Forum extends React.Component<IProps, IState> {
   }
 
   render() {
-    const { handles, categories, posts, threads, status } = this.props;
+    const { categories, posts, threads, status } = this.props;
     const { categoryId, threadId, searchTerm } = this.state;
 
     const category = categories.find((c) => c.id === categoryId);
@@ -122,7 +121,6 @@ class Forum extends React.Component<IProps, IState> {
           posts={posts}
           category={category}
           thread={thread}
-          handles={handles}
           startTime={status.startTime}
           searchTerm={searchTerm}
           filterPosts={this.filterPosts}

+ 0 - 1
src/components/Members/NotFound.tsx

@@ -1,5 +1,4 @@
 import React from "react";
-import { Link } from "react-router-dom";
 import { Back } from "..";
 
 const NotFound = (props: { nolink?: boolean }) => {

+ 2 - 3
src/components/Members/index.tsx

@@ -1,12 +1,11 @@
 import React from "react";
-import { Handles, Member, Post, ProposalDetail, Seat } from "../../types";
+import { Member, Post, ProposalDetail, Seat } from "../../types";
 import MemberBox from "./MemberBox";
 import Loading from "../Loading";
 
 interface IProps {
   councils: Seat[][];
   members: Member[];
-  handles: Handles;
   proposals: ProposalDetail[];
   posts: Post[];
   validators: string[];
@@ -29,7 +28,7 @@ class Members extends React.Component<IProps, IState> {
   }
 
   render() {
-    const { getMember,councils, handles, members, posts, proposals, status } = this.props;
+    const { getMember,councils,  members, posts, proposals, status } = this.props;
     let unique: Member[] = [];
     members.forEach(
       (m) => unique.find((member) => member.id === m.id) || unique.push(m)

+ 1 - 1
src/components/Proposals/Posts.tsx

@@ -3,7 +3,7 @@ import { OverlayTrigger, Tooltip } from "react-bootstrap";
 import { MessageSquare } from "react-feather";
 
 const Posts = (props: { posts: any[] }) => {
-  const { posts } = props;
+  const { posts=[] } = props;
   if (!posts.length) return <div></div>;
   return (
     <div className="float-left">

+ 2 - 2
src/components/Proposals/ProposalOverlay.tsx

@@ -15,7 +15,7 @@ const ProposalOverlay = (props: {
   description: string;
   result: string;
 }) => {
-  const { block, createdAt, message, parameters, result, title } = props;
+  const { block, createdAt, text, parameters, result, title } = props;
 
   const remainingBlocks = +createdAt + +parameters.votingPeriod - block;
   const remainingTime = remainingBlocks * 6;
@@ -36,7 +36,7 @@ const ProposalOverlay = (props: {
           )}
 
           <div className="my-2 p-1 bg-light  text-secondary text-left">
-            {message.split(/\n/).map((line: string, i: number) => (
+            {text?.split(/\n/).map((line: string, i: number) => (
               <div key={i}>{htmr(line)}</div>
             ))}
           </div>

+ 39 - 22
src/components/Proposals/ProposalTable.tsx

@@ -1,21 +1,21 @@
 import React from "react";
+import { Button } from "react-bootstrap";
 import Head from "./TableHead";
 import Row from "./Row";
 import NavBar from "./NavBar";
 import NavButtons from "./NavButtons";
 import Types from "./Types";
-import { Member, Post, ProposalDetail, ProposalPost, Seat } from "../../types";
+import { Council, Member, Post, ProposalDetail, Status } from "../../types";
 
 interface IProps {
   hideNav?: boolean;
   block: number;
   members: Member[];
   proposals: ProposalDetail[];
-  proposalPosts: ProposalPost[];
-  startTime: number;
+  status: Status;
 
   // author overlay
-  councils: Seat[][];
+  councils: Council[];
   posts: Post[];
   validators: string[];
 }
@@ -99,7 +99,16 @@ class ProposalTable extends React.Component<IProps, IState> {
   }
 
   render() {
-    const { hideNav, block, councils, members, posts } = this.props;
+    const {
+      fetchProposals,
+      hideNav,
+      block,
+      council,
+      councils,
+      members,
+      posts,
+      status,
+    } = this.props;
 
     const { page, perPage, author, selectedTypes } = this.state;
 
@@ -133,7 +142,6 @@ class ProposalTable extends React.Component<IProps, IState> {
     const avgDays = Math.floor(avgBlocks / 14400);
     const avgHours = Math.floor((avgBlocks - avgDays * 14400) / 600);
 
-    if (!proposals.length) return <div />;
     return (
       <div className="h-100 overflow-hidden">
         <NavBar
@@ -169,22 +177,31 @@ class ProposalTable extends React.Component<IProps, IState> {
         />
 
         <div className="d-flex flex-column overflow-auto p-2">
-          {proposals.slice((page - 1) * perPage, page * perPage).map((p) => (
-            <Row
-              key={p.id}
-              {...p}
-              block={block}
-              members={members}
-              startTime={this.props.startTime}
-              posts={this.props.proposalPosts.filter(
-                (post) => post.threadId === p.id
-              )}
-              councils={councils}
-              forumPosts={posts}
-              proposals={this.props.proposals}
-              validators={this.props.validators}
-            />
-          ))}
+          {!proposals.length ? (
+            <div>
+              No proposals cached.{" "}
+              <Button variant="secondary" onClick={fetchProposals}>
+                Fetch
+              </Button>{" "}
+            </div>
+          ) : (
+            proposals
+              .slice((page - 1) * perPage, page * perPage)
+              .map((p) => (
+                <Row
+                  key={p.id}
+                  {...p}
+                  block={block}
+                  council={council}
+                  members={members}
+                  startTime={status.startTime}
+                  councils={councils}
+                  posts={posts}
+                  proposals={this.props.proposals}
+                  validators={this.props.validators}
+                />
+              ))
+          )}
         </div>
         <NavButtons
           setPage={this.setPage}

+ 61 - 35
src/components/Proposals/Row.tsx

@@ -1,27 +1,23 @@
 import React from "react";
-import { Badge } from "react-bootstrap";
-import MemberOverlay from "../Members/MemberOverlay";
+import { Badge, Button } from "react-bootstrap";
+import { Link, makeStyles } from "@material-ui/core";
+import moment from "moment";
+
+import { InfoTooltip, MemberOverlay, VoteNowButton, VotesBubbles } from "..";
 import Bar from "./Bar";
 import Posts from "./Posts";
 import Detail from "./Detail";
-import { VoteNowButton, VotesBubbles } from "..";
-import moment from "moment";
+import { formatDate } from "../../lib/util";
 
+import { ProposalParameters, VotingResults } from "@joystream/types/proposals";
 import {
+  Council,
   Member,
   Post,
   ProposalDetail,
   ProposalPost,
-  Seat,
   Vote,
 } from "../../types";
-import { ProposalParameters, VotingResults } from "@joystream/types/proposals";
-import InfoTooltip from "../Tooltip";
-import { Link, makeStyles } from "@material-ui/core";
-
-const formatTime = (time: number) => {
-  return moment(time).format("DD/MM/YYYY HH:mm");
-};
 
 const colors: { [key: string]: string } = {
   Approved: "success",
@@ -46,6 +42,7 @@ const ProposalRow = (props: {
   finalizedAt: number;
   startTime: number;
   description: string;
+  domain: string;
   author: string;
   id: number;
   parameters: ProposalParameters;
@@ -56,17 +53,21 @@ const ProposalRow = (props: {
   type: string;
   votes: VotingResults;
   members: Member[];
-  posts: ProposalPost[];
   votesByAccount?: Vote[];
   detail?: any;
   // author overlay
-  councils: Seat[][];
-  forumPosts: Post[];
+  councils: Council[];
+  council: Council;
+  posts: Post[];
   proposals: ProposalDetail[];
+  proposalPosts?: ProposalPost[];
   validators: string[];
 }) => {
   const {
     block,
+    council,
+    councils,
+    domain,
     createdAt,
     description,
     executed,
@@ -75,16 +76,19 @@ const ProposalRow = (props: {
     id,
     title,
     type,
+    posts,
+    proposals,
+    proposalPosts,
     votes,
     detail,
+    startTime,
+    validators,
   } = props;
 
-  const url = `https://pioneer.joystreamstats.live/#/proposals/${id}`;
   let result: string = props.result ? props.result : props.stage;
   if (executed) result = Object.keys(props.executed)[0];
 
-  const finalized =
-    finalizedAt && formatTime(props.startTime + finalizedAt * 6000);
+  const finalized = finalizedAt && formatDate(startTime + finalizedAt * 6000);
 
   const period = +props.parameters.votingPeriod;
   let blocks = finalizedAt ? finalizedAt - createdAt : block - createdAt;
@@ -93,14 +97,32 @@ const ProposalRow = (props: {
   const moments = (blocks: number) => moment().add(blocks).fromNow();
   const expires = moments(6000 * (period - blocks));
   const age = moments(-blocks * 6000);
-  const time = formatTime(props.startTime + createdAt * 6000);
+  const time = formatDate(startTime + createdAt * 6000);
   const created = blocks ? `${age} at ${time}` : time;
   const left = `${period - blocks} / ${period} blocks left (${percent}%)`;
   const classes = useStyles();
+
+  const hasToVote = council?.consuls.filter(
+    (c) => !votes.find((v) => v.member.handle === c.member.handle)
+  );
+
   return (
     <div className="box d-flex flex-column">
       <div className="d-flex flex-row justify-content-left text-left mt-3">
-        <div className="col-5 col-md-3 text-left">
+        <Badge className={`bg-${colors[result]} col-2 d-md-block`}>
+          <div className={`-${colors[result]} my-2`}>
+            <b>{result}</b>
+          </div>
+          {finalized ? (
+            finalized
+          ) : (
+            <div>
+              <VoteNowButton show={true} url={`${domain}/#/proposals/${id}`} />
+            </div>
+          )}
+        </Badge>
+
+        <div className="col-5 text-left">
           <InfoTooltip
             placement="bottom"
             id={`overlay-${author}`}
@@ -108,11 +130,11 @@ const ProposalRow = (props: {
               <MemberOverlay
                 handle={author.handle}
                 member={author}
-                councils={props.councils}
-                proposals={props.proposals}
-                posts={props.forumPosts}
-                startTime={props.startTime}
-                validators={props.validators}
+                councils={councils}
+                proposals={proposals}
+                posts={posts}
+                startTime={startTime}
+                validators={validators}
               />
             }
           >
@@ -127,22 +149,26 @@ const ProposalRow = (props: {
               <Link className={classes.link} href={`/proposals/${id}`}>
                 {title}
               </Link>
-              <Posts posts={props.posts} />
+              <Posts posts={proposalPosts} />
             </b>
           </InfoTooltip>
           <Detail detail={detail} type={type} />
         </div>
 
-        <Badge className={`bg-${colors[result]} col-2 p-3 d-md-block ml-auto`}>
-          <div className={`bg-${colors[result]} mb-2`}>
-            <b>{result}</b>
-          </div>
+        <div className="d-flex flex-wrap p-2">
+          <VotesBubbles votes={votes} />
 
-          {finalized ? finalized : <VoteNowButton show={true} url={url} />}
-        </Badge>
-      </div>
-      <div className="d-flex flex-row p-2">
-        <VotesBubbles votes={votes} />
+          {hasToVote.map((c) => (
+            <Button
+              key={c.id}
+              variant="outline-secondary"
+              className="btn-sm p-1"
+              title={`${c.member.handle} did not vote.`}
+            >
+              {c.member.handle}
+            </Button>
+          ))}
+        </div>
       </div>
 
       <Bar

+ 19 - 20
src/components/Proposals/index.tsx

@@ -1,12 +1,10 @@
 import React from "react";
-import { Member, ProposalDetail, Post, ProposalPost, Seat } from "../../types";
-import Loading from "..//Loading";
-import ProposalTable from "./ProposalTable";
+import { ProposalTable } from "..";
+import { Member, ProposalDetail, Post, Seat } from "../../types";
 
 const Proposals = (props: {
   status: { startTime: number };
   proposals: ProposalDetail[];
-  proposalPosts: ProposalPost[];
   members: Member[];
 
   // author overlay
@@ -14,33 +12,34 @@ const Proposals = (props: {
   posts: Post[];
   validators: string[];
 }) => {
-  const { proposalPosts, members, status } = props;
+  const {
+    fetchProposals,
+    posts,
+    councils,
+    members,
+    status,
+    validators,
+  } = props;
+  const { council, startTime } = status;
 
-  // prepare proposals
-  //  - remove empty
+  // TODO put on state sorted, remove empty
   const proposals = props.proposals
     .filter((p) => p)
     .sort((a, b) => b.id - a.id);
 
-  // - communicate loading state
-  if (!proposals.length)
-    return (
-      <div className="box">
-        <Loading target="Proposals" />
-      </div>
-    );
-
   // - list all proposals
   return (
     <ProposalTable
+      fetchProposals={fetchProposals}
       block={status.block.id}
       members={members}
       proposals={proposals}
-      proposalPosts={proposalPosts}
-      startTime={status.startTime}
-      councils={props.councils}
-      posts={props.posts}
-      validators={props.validators}
+      startTime={startTime}
+      council={council}
+      councils={councils}
+      posts={posts}
+      validators={validators}
+      status={status}
     />
   );
 };

+ 1 - 2
src/components/Validators/Nominators.tsx

@@ -1,6 +1,6 @@
 import React from "react";
 import User from "../User";
-import { Handles, Stake } from "../../types";
+import { Stake } from "../../types";
 
 // TODO use MemberBox after refactor
 
@@ -16,7 +16,6 @@ const Nominators = (props: {
   sortBy: (field: string) => void;
   toggleExpand: () => void;
   expand: boolean;
-  handles: Handles;
   nominators?: Stake[];
   reward: number;
 }) => {

+ 0 - 3
src/components/Validators/Validator.tsx

@@ -3,7 +3,6 @@ import { Activity, Star } from "react-feather";
 import Nominators from "./Nominators";
 import MemberBox from "../Members/MemberBox";
 import {
-  Handles,
   Member,
   Post,
   ProposalDetail,
@@ -18,7 +17,6 @@ interface IProps {
   sortBy: (sortBy: string) => void;
   validator: string;
   councils: Seat[][];
-  handles: Handles;
   members: Member[];
   posts: Post[];
   proposals: ProposalDetail[];
@@ -168,7 +166,6 @@ class Validator extends Component<IProps, IState> {
             sortBy={sortBy}
             expand={expandNominators}
             nominators={stake ? stake.others : undefined}
-            handles={handles}
           />
         </div>
       </div>

+ 0 - 2
src/components/Validators/Waiting.tsx

@@ -21,7 +21,6 @@ const Waiting = (props: {}) => {
     show,
     waiting,
     councils,
-    handles,
     members,
     posts,
     proposals,
@@ -45,7 +44,6 @@ const Waiting = (props: {}) => {
             account={v}
             placement={"top"}
             councils={councils}
-            handle={handles[v]}
             members={members}
             posts={posts}
             proposals={proposals}

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

@@ -6,7 +6,6 @@ import Waiting from "./Waiting";
 import Loading from "../Loading";
 
 import {
-  Handles,
   Member,
   Post,
   ProposalDetail,
@@ -29,7 +28,6 @@ import {
 
 interface IProps {
   councils: Seat[][];
-  handles: Handles;
   members: Member[];
   posts: Post[];
   proposals: ProposalDetail[];
@@ -118,7 +116,6 @@ const Validators = (iProps: IProps) => {
   const {
     getMember,
     councils,
-    handles,
     members,
     posts,
     proposals,
@@ -243,7 +240,6 @@ const Validators = (iProps: IProps) => {
             posts={posts}
             proposals={proposals}
             members={members}
-            handles={handles}
           />
         </div>
       </Paper>

+ 8 - 6
src/components/Votes.tsx

@@ -20,7 +20,7 @@ export const voteStyles: { [key: string]: string } = {
 export const VoteButton = (props: { handle: string; vote: string }) => {
   const { handle, vote } = props;
   return (
-    <Button title={vote} className="m-1" variant={voteStyles[vote]}>
+    <Button title={vote} className="btn-sm p-1" variant={voteStyles[vote]}>
       {handle}
     </Button>
   );
@@ -47,10 +47,12 @@ const VoteBubble = (props: {
   count: number;
 }) => {
   const { detailed, vote } = props;
-
+  const handle = vote.member.handle;
   return (
-    <Button className="btn-sm m-1" variant={voteStyles[vote.vote]}>
-      {vote.member.handle} {detailed && vote.vote}
+    <Button className="btn-sm p-1" variant={voteStyles[vote.vote]}>
+      <a href={`/members/${handle}`}>
+        {handle} {detailed && vote.vote}
+      </a>
     </Button>
   );
 };
@@ -59,7 +61,7 @@ export const VotesBubbles = (props: { detailed?: boolean; votes: Vote[] }) => {
   const { detailed, votes } = props;
 
   return (
-    <div>
+    <>
       {votes.map((vote: Vote) => (
         <VoteBubble
           key={vote.id}
@@ -68,7 +70,7 @@ export const VotesBubbles = (props: { detailed?: boolean; votes: Vote[] }) => {
           count={votes.length}
         />
       ))}
-    </div>
+    </>
   );
 };
 

+ 4 - 0
src/components/index.ts

@@ -7,15 +7,19 @@ export { default as Councils } from "./Councils";
 export { default as Curation } from "./Curation";
 export { default as Dashboard } from "./Dashboard";
 export { default as Forum } from "./Forum";
+export { default as InfoTooltip } from "./Tooltip";
 export { default as Mint } from "./Mint";
 export { default as Spending } from "./Proposals/Spending";
 export { default as Proposals } from "./Proposals";
+export { default as ProposalTable } from "./Proposals/ProposalTable";
 export { default as ProposalLink } from "./Proposals/ProposalLink";
+export { default as ProposalOverlay } from "./Proposals/ProposalOverlay";
 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 Member } from "./Members/Member";
+export { default as MemberOverlay } from "./Members/MemberOverlay";
 export { default as Members } from "./Members";
 export { default as Storage } from "./Storage";
 export { default as Tokenomics } from "./Tokenomics";

+ 4 - 0
src/lib/util.ts

@@ -45,6 +45,10 @@ export const printStatus = (
 };
 
 // time
+export const formatDate = (time?: number) => {
+  return moment(time).format("DD/MM/YYYY HH:mm");
+};
+
 export const formatTime = (time?: any): string =>
   moment(time).format("H:mm:ss");