Joystream Stats 4 gadi atpakaļ
vecāks
revīzija
63c5be044b

+ 5 - 3
src/App.tsx

@@ -1,5 +1,4 @@
 import React from "react";
-
 import "bootstrap/dist/css/bootstrap.min.css";
 import "./index.css";
 import { Routes, Loading } from "./components";
@@ -419,7 +418,8 @@ class App extends React.Component<IProps, IState> {
     if (exists) return exists;
 
     const id = await get.memberIdByAccount(api, account);
-    if (!id) return { id: -1, handle: `unknown`, account };
+    if (!id)
+      return { id: -1, handle: `unknown`, account, about: ``, registeredAt: 0 };
     return await this.fetchMember(api, id);
   }
   async fetchMember(api: Api, id: MemberId | number): Promise<Member> {
@@ -430,7 +430,9 @@ class App extends React.Component<IProps, IState> {
 
     const handle = String(membership.handle);
     const account = String(membership.root_account);
-    const member: Member = { id, handle, account };
+    const about = String(membership.about);
+    const registeredAt = Number(membership.registered_at_block);
+    const member: Member = { id, handle, account, registeredAt, about };
     const members = this.state.members.concat(member);
 
     if (members.length) this.save(`members`, members);

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

@@ -81,9 +81,8 @@ const CouncilVotes = (props: {
   let councilMembers: Member[] = [];
   council.forEach((id) => {
     const member = members.find((m) => m.id === id);
-    councilMembers.push(
-      member || { id, handle: String(id), account: String(id) }
-    );
+    if (!member) return;
+    councilMembers.push(member);
   });
 
   const fail = "btn btn-outline-danger";

+ 83 - 0
src/components/Members/Member.tsx

@@ -0,0 +1,83 @@
+import React from "react";
+import { Link } from "react-router-dom";
+import { Member, Post, ProposalDetail } from "../../types";
+import { domain } from "../../config";
+
+interface Vote {
+  proposal: ProposalDetail;
+  vote: string;
+}
+
+const NotFound = () => {
+  return (
+    <div className="box">
+      <div> Member not found</div>
+      <Link to={`/members`}>Back</Link>
+    </div>
+  );
+};
+
+const MemberBox = (props: {
+  match?: { params: { handle: string } };
+  handle?: string;
+  members: Member[];
+  councils: number[][];
+  proposals: ProposalDetail[];
+  posts: Post[];
+}) => {
+  const { councils, members, proposals } = props;
+  const handle = props.handle
+    ? props.handle
+    : props.match
+    ? props.match.params.handle
+    : `?`;
+  const member = members.find((m) => m.handle === handle);
+  if (!member) return <NotFound />;
+
+  const id = Number(member.id);
+  const isCouncilMember = councils[councils.length - 1].includes(id);
+  const onCouncil = councils.filter((c) => c.includes(Number(member.id)));
+
+  let votes: Vote[] = [];
+  proposals.forEach((p) => {
+    if (!p || !p.votesByMemberId) return;
+    const vote = p.votesByMemberId.find((v) => v.memberId === id);
+    if (vote && vote.vote !== ``) votes.push({ proposal: p, vote: vote.vote });
+  });
+  const createdProposals = proposals.filter((p) => p && p.author === handle);
+
+  const posts = props.posts.filter(
+    (p) => p.authorId === String(member.account)
+  );
+
+  return (
+    <div className="box">
+      {props.match && (
+        <Link className="float-left" to={"/members"}>
+          back
+        </Link>
+      )}
+      {isCouncilMember && <div>council member</div>}
+      <a href={`${domain}/#/members/${handle}`}>
+        <h1>{handle}</h1>
+      </a>
+
+      <div className="text-left">
+        <div>Id: {member.id}</div>
+        <div>Account: {member.account}</div>
+        <div>Registered at block: {member.registeredAt}</div>
+
+        <div>Council member: {onCouncil.length} times</div>
+        <div>
+          Proposal votes: <Link to={`/councils`}>{votes.length}</Link>
+        </div>
+        <div>Created Proposals: {createdProposals.length}</div>
+        <div>Posts: {posts.length}</div>
+
+        <div>About: {member.about}</div>
+      </div>
+    </div>
+  );
+};
+
+export default MemberBox;

+ 37 - 0
src/components/Members/MemberBox.tsx

@@ -0,0 +1,37 @@
+import React from "react";
+import { OverlayTrigger, Tooltip } from "react-bootstrap";
+//import { Link } from "react-router-dom";
+import { Member, Post, ProposalDetail } from "../../types";
+import MemberOverlay from "./Member";
+
+const MemberBox = (props: {
+  councils: number[][];
+  members: Member[];
+  proposals: ProposalDetail[];
+  posts: Post[];
+  id: number;
+  account: string;
+  handle: string;
+}) => {
+  const { councils, handle, members, posts, proposals } = props;
+  return (
+    <OverlayTrigger
+      placement="bottom"
+      overlay={
+        <Tooltip id={`overlay-${handle}`}>
+          <MemberOverlay
+            handle={handle}
+            members={members}
+            councils={councils}
+            proposals={proposals}
+            posts={posts}
+          />
+        </Tooltip>
+      }
+    >
+      <div className="box">{handle}</div>
+    </OverlayTrigger>
+  );
+};
+
+export default MemberBox;

+ 39 - 0
src/components/Members/Votes.tsx

@@ -0,0 +1,39 @@
+import React from "react";
+import { ProposalDetail } from "../../types";
+
+interface Vote {
+  proposal: ProposalDetail;
+  vote: string;
+}
+
+const Votes = (props: { votes: Vote[] }) => {
+  const { votes } = props;
+  if (!votes.length) return <div />;
+
+  return (
+    <div>
+      <h2>Votes</h2>
+      <div className="">
+        {votes.map((v) => (
+          <VoteDiv key={v.proposal.id} vote={v} />
+        ))}
+      </div>
+    </div>
+  );
+};
+
+const VoteDiv = (props: { vote: Vote }) => {
+  const { proposal, vote } = props.vote;
+  if (vote === "") return <div />;
+
+  return (
+    <div
+      key={proposal.id}
+      className={vote === `Approve` ? `bg-success` : `bg-danger`}
+    >
+      {proposal.title}
+    </div>
+  );
+};
+
+export default Votes

+ 100 - 0
src/components/Members/index.tsx

@@ -0,0 +1,100 @@
+import React from "react";
+//import { Link } from "react-router-dom";
+import { Handles, Member, Post, ProposalDetail } from "../../types";
+import MemberBox from "./MemberBox";
+import Loading from "../Loading";
+import Back from "../Back";
+
+interface IProps {
+  councils: number[][];
+  members: Member[];
+  handles: Handles;
+  proposals: ProposalDetail[];
+  posts: Post[];
+}
+
+interface IState {
+  memberId: number;
+}
+
+class Members extends React.Component<IProps, IState> {
+  constructor(props: IProps) {
+    super(props);
+    this.state = { memberId: 0 };
+  }
+
+  selectMember(memberId: number) {
+    this.setState({ memberId });
+  }
+
+  render() {
+    const { councils, handles, members, posts, proposals } = this.props;
+    let uniqueMembers: Member[] = [];
+    members.forEach(
+      (m) =>
+        uniqueMembers.find((member) => member.id === m.id) ||
+        uniqueMembers.push(m)
+    );
+    uniqueMembers = uniqueMembers.sort((a, b) => Number(a.id) - Number(b.id));
+
+    if (!uniqueMembers.length) return <Loading />;
+
+    const third = members.length / 3;
+    const row1 = uniqueMembers.slice(0, third + 1);
+    const row2 = uniqueMembers.slice(third + 1, 2 * third + 1);
+    const row3 = uniqueMembers.slice(2 * third + 1, members.length);
+
+    return (
+      <div>
+        <Back />
+        <h1 className="text-center text-white">Joystream Members</h1>
+        <div className="d-flex flew-row justify-content-around">
+          <div className="col-4 d-flex flex-column">
+            {row1.map((m) => (
+              <MemberBox
+                key={String(m.account)}
+                id={Number(m.id)}
+                account={String(m.account)}
+                handle={m.handle || handles[String(m.account)]}
+                members={members}
+                councils={councils}
+                proposals={proposals}
+                posts={posts}
+              />
+            ))}
+          </div>
+          <div className="col-4 d-flex flex-column">
+            {row2.map((m) => (
+              <MemberBox
+                key={String(m.account)}
+                id={Number(m.id)}
+                account={String(m.account)}
+                handle={m.handle || handles[String(m.account)]}
+                members={members}
+                councils={councils}
+                proposals={proposals}
+                posts={posts}
+              />
+            ))}
+          </div>
+          <div className="col-4 d-flex flex-column">
+            {row3.map((m) => (
+              <MemberBox
+                key={String(m.account)}
+                id={Number(m.id)}
+                account={String(m.account)}
+                handle={m.handle || handles[String(m.account)]}
+                members={members}
+                councils={councils}
+                proposals={proposals}
+                posts={posts}
+              />
+            ))}
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Members;

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

@@ -1,5 +1,15 @@
 import { Switch, Route } from "react-router-dom";
-import { Councils, Dashboard, Forum, Mint, Proposals, Proposal, Tokenomics } from "..";
+import {
+  Councils,
+  Dashboard,
+  Forum,
+  Member,
+  Members,
+  Mint,
+  Proposals,
+  Proposal,
+  Tokenomics,
+} from "..";
 import { IState } from "../../types";
 
 const Routes = (props: IState) => {
@@ -18,6 +28,11 @@ const Routes = (props: IState) => {
       <Route path="/councils" render={() => <Councils {...props} />} />
       <Route path="/forum" render={() => <Forum {...props} />} />
       <Route path="/mint" render={() => <Mint {...props} />} />
+      <Route
+        path="/members/:handle"
+        render={(routeprops) => <Member {...routeprops} {...props} />}
+      />
+      <Route path="/members" render={() => <Members {...props} />} />{" "}
       <Route path="/" render={() => <Dashboard {...props} />} />
     </Switch>
   );

+ 2 - 0
src/components/index.ts

@@ -11,4 +11,6 @@ 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 Members } from "./Members";
 export { default as Tokenomics } from "./Tokenomics";

+ 9 - 0
src/index.css

@@ -74,3 +74,12 @@ table td {
 .user {
   min-width: 75px;
 }
+
+.tooltip-inner div {
+    width: 400px !important;
+}
+.tooltip, .tooltip-inner {
+    background: none;
+    padding: 0 !important;
+    margin-left: -1px;
+}

+ 2 - 0
src/types.ts

@@ -146,6 +146,8 @@ export interface Member {
   account: AccountId | string;
   handle: string;
   id: MemberId | number;
+  registeredAt: number;
+  about: string;
 }
 
 export interface Block {