@@ -0,0 +1,241 @@
1+import React, { useCallback, useMemo, useState } from 'react';
2+import cn from 'classnames';
3+import { Grid, Text, Input, P, LinkButton } from 'indigo-react';
4+import * as azimuth from 'azimuth-js';
5+import ob from 'urbit-ob';
6+
7+import { useNetwork } from 'store/network';
8+import { usePointCache } from 'store/pointCache';
9+import { usePointCursor } from 'store/pointCursor';
10+
11+import * as need from 'lib/need';
12+import useEthereumTransaction from 'lib/useEthereumTransaction';
13+import { GAS_LIMITS } from 'lib/constants';
14+import patp2dec from 'lib/patp2dec';
15+import { getSpawnCandidate } from 'lib/child';
16+import { useLocalRouter } from 'lib/LocalRouter';
17+import useConstant from 'lib/useConstant';
18+
19+import { keccak256 } from 'lib/wallet';
20+
21+import ViewHeader from 'components/ViewHeader';
22+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
23+import View from 'components/View';
24+import { PointInput, AddressInput } from 'form/Inputs';
25+import {
26+ composeValidator,
27+ buildPointValidator,
28+ buildAddressValidator,
29+ hasErrors,
30+} from 'form/validators';
31+import BridgeForm from 'form/BridgeForm';
32+import FormError from 'form/FormError';
33+import CopiableAddress from 'components/CopiableAddress';
34+import convertToInt from 'lib/convertToInt';
35+
36+function useCastDocumentVote() {
37+ const { contracts } = useNetwork();
38+ const { pointCursor } = usePointCursor();
39+ const { syncDetails } = usePointCache();
40+
41+ const _contracts = need.contracts(contracts);
42+ const _point = need.point(pointCursor);
43+
44+ return useEthereumTransaction(
45+ useCallback(
46+ (hash, accept) => {
47+ return azimuth.ecliptic.castDocumentVote(
48+ _contracts,
49+ _point,
50+ hash,
51+ accept
52+ );
53+ },
54+ [_contracts, _point]
55+ ),
56+ useCallback(() => null, []), //TODO update polls[hash].hasVoted ?
57+ GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
58+ );
59+}
60+
61+export default function Senate() {
62+ const { pop } = useLocalRouter();
63+ const { contracts } = useNetwork();
64+ const { pointCursor } = usePointCursor();
65+
66+ const _contracts = need.contracts(contracts);
67+ const _point = convertToInt(need.point(pointCursor), 10);
68+
69+ const {
70+ construct,
71+ unconstruct,
72+ inputsLocked,
73+ completed,
74+ bind,
75+ } = useCastDocumentVote();
76+
77+ const [documentHash, setDocumentHash] = useState('0x...');
78+ const [majorities, setMajorities] = useState([]); //TODO gh.com/proposals/doc.txt
79+ const [proposals, setProposals] = useState([]);
80+ const [polls, setPolls] = useState({});
81+ const [loading, setLoading] = useState(true);
82+ const [votingOn, setVotingOn] = useState(null);
83+
84+ //TODO update every second?
85+ const now = Math.round(new Date().getTime() / 1000);
86+
87+ useMemo(async () => {
88+ let proposals = await azimuth.polls.getDocumentProposals(_contracts);
89+ let majorities = await azimuth.polls.getDocumentMajorities(_contracts);
90+ let polls = {};
91+ for (let doc of proposals) {
92+ let poll = await azimuth.polls.getDocumentPoll(_contracts, doc);
93+ console.log('got poll', doc, poll);
94+ poll.endTime = polls.start + poll.duration;
95+ poll.hasVoted = await azimuth.polls.hasVotedOnDocumentPoll(
96+ _contracts,
97+ _point,
98+ doc
99+ );
100+ polls[doc] = poll;
101+ }
102+ setMajorities(majorities);
103+ setProposals(proposals);
104+ console.log('setting polls', polls);
105+ setPolls(polls);
106+ setLoading(false);
107+ return;
108+ }, [_contracts]);
109+
110+ const [open, closed] = useMemo(() => {
111+ let open = [];
112+ let closed = [];
113+ if (loading) return [open, closed];
114+ console.log('proposals', proposals);
115+ console.log('polls', polls);
116+ for (let doc of proposals) {
117+ if (now > polls[doc].endTime) {
118+ closed.push(doc);
119+ } else {
120+ open.push(doc);
121+ }
122+ }
123+ return [open, closed];
124+ }, [loading, proposals, polls, now]);
125+
126+ const doVote = useCallback(
127+ (hash, accept) => {
128+ construct(hash, accept);
129+ setVotingOn(hash);
130+ },
131+ [construct, setVotingOn]
132+ );
133+
134+ const didVote = useCallback(() => {
135+ polls[votingOn].hasVoted = true;
136+ setPolls(polls);
137+ }, [polls, setPolls, votingOn]);
138+
139+ const majorityList = useMemo(() => {
140+ return majorities.map(doc => {
141+ return (
142+ <Text>
143+ <a
144+ target="_blank"
145+ rel="noopener noreferrer"
146+ href={`https://github.com/urbit/azimuth/blob/master/proposals/${doc}.txt`}>
147+ <code>{doc}</code>
148+ </a>
149+ </Text>
150+ );
151+ });
152+ }, [majorities]);
153+
154+ const openList = useMemo(() => {
155+ return open.map(doc => {
156+ const hasVoted = polls[doc].hasVoted;
157+ return (
158+ <>
159+ <Grid.Item full as={Text}>
160+ <P>
161+ <code>{doc}</code>
162+ </P>
163+ <P>
164+ {hasVoted ? (
165+ 'Your vote has been cast.'
166+ ) : (
167+ <>
168+ <LinkButton onClick={() => doVote(doc, true)}>aye</LinkButton>
169+ {' / '}
170+ <LinkButton onClick={() => doVote(doc, false)}>
171+ nay
172+ </LinkButton>
173+ </>
174+ )}
175+ </P>
176+ </Grid.Item>
177+ <Grid.Divider />
178+ </>
179+ );
180+ });
181+ }, [open, polls]);
182+
183+ const onDocumentChange = useCallback(({ valid, values }) => {
184+ if (values.document) {
185+ const hash = '0x' + keccak256(values.document.trim()).toString('hex');
186+ setDocumentHash(hash);
187+ } else {
188+ setDocumentHash('0x...');
189+ }
190+ }, []);
191+
192+ return (
193+ <View pop={pop} inset>
194+ <Grid>
195+ <Grid.Item full as={ViewHeader}>
196+ Senate: Document Proposals
197+ </Grid.Item>
198+
199+ <Grid.Item full as={P}>
200+ Accepted documents:
201+ {majorityList}
202+ </Grid.Item>
203+
204+ <Grid>
205+ <Grid.Item full as={P}>
206+ Open document polls:
207+ </Grid.Item>
208+ {openList}
209+ </Grid>
210+
211+ <BridgeForm onValues={onDocumentChange}>
212+ {({ onSubmit, values }) => (
213+ <>
214+ <Grid.Item
215+ full
216+ as={InlineEthereumTransaction}
217+ {...bind}
218+ onReturn={didVote}
219+ />
220+
221+ <Grid.Divider />
222+
223+ <Grid.Item
224+ full
225+ as={Input}
226+ type="textarea"
227+ placeholder="Input some text to find its hash..."
228+ name="document"
229+ className="mt4"
230+ />
231+
232+ <Grid.Item full as={CopiableAddress}>
233+ {documentHash}
234+ </Grid.Item>
235+ </>
236+ )}
237+ </BridgeForm>
238+ </Grid>
239+ </View>
240+ );
241+}