on df0c310 help sign in
bridge/src/views/Senate/Documents.jsx
215 lines 6.2 kB history raw
1import React, { useCallback, useMemo, useEffect, useState } from 'react';
2import { Grid, Text, P, LinkButton } from 'indigo-react';
3import * as azimuth from 'azimuth-js';
5import { useNetwork } from 'store/network';
6import { usePointCursor } from 'store/pointCursor';
8import * as need from 'lib/need';
9import useEthereumTransaction from 'lib/useEthereumTransaction';
10import { GAS_LIMITS } from 'lib/constants';
11import { useLocalRouter } from 'lib/LocalRouter';
13import ViewHeader from 'components/ViewHeader';
14import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15import View from 'components/View';
16import BridgeForm from 'form/BridgeForm';
17import { convertToInt } from 'lib/convertToInt';
19function useCastDocumentVote() {
20 const { contracts } = useNetwork();
21 const { pointCursor } = usePointCursor();
23 const _contracts = need.contracts(contracts);
24 const _point = need.point(pointCursor);
26 return useEthereumTransaction(
27 useCallback(
28 (hash, accept) => {
29 return azimuth.ecliptic.castDocumentVote(
30 _contracts,
31 _point,
32 hash,
33 accept
34 );
35 },
36 [_contracts, _point]
37 ),
38 useCallback(() => null, []), //TODO update polls[hash].hasVoted ?
39 GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
40 );
43function renderDocumentHash(doc) {
44 return doc.slice(0, 34) + '\n ' + doc.slice(34);
47export default function Documents() {
48 const { pop } = useLocalRouter();
49 const { contracts } = useNetwork();
50 const { pointCursor } = usePointCursor();
52 const _contracts = need.contracts(contracts);
53 const _point = convertToInt(need.point(pointCursor), 10);
55 const { construct, reset, unconstruct, bind } = useCastDocumentVote();
57 // const [documentHash, setDocumentHash] = useState('0x...');
58 const [majorities, setMajorities] = useState([]);
59 const [proposals, setProposals] = useState([]);
60 const [polls, setPolls] = useState({});
61 const [loading, setLoading] = useState(true);
62 const [votingOn, setVotingOn] = useState({});
64 //TODO update every second?
65 const now = Math.round(new Date().getTime() / 1000);
67 useEffect(() => {
68 (async () => {
69 let proposals = await azimuth.polls.getDocumentProposals(_contracts);
70 let majorities = await azimuth.polls.getDocumentMajorities(_contracts);
71 let polls = {};
72 for (let doc of proposals) {
73 let poll = await azimuth.polls.getDocumentPoll(_contracts, doc);
74 poll.endTime = convertToInt(poll.start) + convertToInt(poll.duration);
75 poll.hasVoted = await azimuth.polls.hasVotedOnDocumentPoll(
76 _contracts,
77 _point,
78 doc
79 );
80 polls[doc] = poll;
81 }
82 setMajorities(majorities);
83 setProposals(proposals);
84 setPolls(polls);
85 setLoading(false);
86 })();
87 }, [_contracts, _point]);
89 // eslint-disable-next-line
90 const [open, closed] = useMemo(() => {
91 let open = [];
92 let closed = [];
93 if (loading) return [open, closed];
94 for (let doc of proposals) {
95 if (now > polls[doc].endTime) {
96 closed.push(doc);
97 } else {
98 open.push(doc);
99 }
100 }
101 return [open, closed];
102 }, [loading, proposals, polls, now]);
104 const doVote = useCallback(
105 (hash, accept) => {
106 construct(hash, accept);
107 setVotingOn({ hash, accept });
108 },
109 [construct]
110 );
112 const didVote = useCallback(() => {
113 reset();
114 unconstruct();
115 setPolls(polls => {
116 polls[votingOn.hash].hasVoted = true;
117 return polls;
118 });
119 setVotingOn({});
120 }, [votingOn, reset, unconstruct]);
122 const majorityList = useMemo(() => {
123 return majorities.map(doc => {
124 return (
125 <Grid.Item full className="mv2" as={P}>
126 <a
127 target="_blank"
128 rel="noopener noreferrer"
129 href={`https://raw.githubusercontent.com/urbit/azimuth/master/proposals/${doc}.txt`}
130 class="mb4 mono wrap pre">
131 {renderDocumentHash(doc)}↗
132 </a>
133 </Grid.Item>
134 );
135 });
136 }, [majorities]);
138 const openList = useMemo(() => {
139 return open.map(doc => {
140 const hasVoted = polls[doc].hasVoted;
141 return (
142 <>
143 <Grid.Item full className="mv2" as={Text}>
144 <P className="m0 mono wrap pre">{renderDocumentHash(doc)}</P>
145 <P className="mt0">
146 {hasVoted ? (
147 'Your vote has been cast.'
148 ) : (
149 <>
150 <LinkButton onClick={() => doVote(doc, true)}>
151 {votingOn.hash === doc && votingOn.accept ? (
152 <b>support</b>
153 ) : (
154 <>support</>
155 )}
156 </LinkButton>
157 {' / '}
158 <LinkButton onClick={() => doVote(doc, false)}>
159 {votingOn.hash === doc && !votingOn.accept ? (
160 <b>reject</b>
161 ) : (
162 <>reject</>
163 )}
164 </LinkButton>
165 </>
166 )}
167 </P>
168 </Grid.Item>
169 <Grid.Divider />
170 </>
171 );
172 });
173 }, [open, polls, votingOn, doVote]);
175 return (
176 <View pop={pop} inset>
177 <Grid>
178 <Grid.Item full as={ViewHeader}>
179 Senate: Document Proposals
180 </Grid.Item>
182 <Grid.Item full as={Grid}>
183 <Grid.Item full as={P}>
184 Accepted documents:
185 </Grid.Item>
186 {majorityList}
187 </Grid.Item>
189 {openList.length > 0 && (
190 <>
191 <Grid.Divider className="mv4" />
193 <Grid.Item full as={Grid}>
194 <Grid.Item full as={P}>
195 Open document polls:
196 </Grid.Item>
197 {openList}
198 </Grid.Item>
200 <BridgeForm>
201 {({ onSubmit, values }) => (
202 <Grid.Item
203 full
204 as={InlineEthereumTransaction}
205 {...bind}
206 onReturn={didVote}
207 />
208 )}
209 </BridgeForm>
210 </>
211 )}
212 </Grid>
213 </View>
214 );