on master help sign in
Merge pull request #569 from urbit/m/ugprade-voting
ae69ddbfang
src/lib/routeNames.js+2
@@ -19,6 +19,8 @@ export const ROUTE_NAMES = {
19 SIGIL_GENERATOR: Symbol('SIGIL_GENERATOR'),
19 SIGIL_GENERATOR: Symbol('SIGIL_GENERATOR'),
20 CREATE_GALAXY: Symbol('CREATE_GALAXY'),
20 CREATE_GALAXY: Symbol('CREATE_GALAXY'),
21 SENATE: Symbol('SENATE'),
21 SENATE: Symbol('SENATE'),
22+ DOCUMENTS: Symbol('DOCUMENTS'),
23+ UPGRADES: Symbol('UPGRADES'),
22 ISSUE_CHILD: Symbol('ISSUE_CHILD'),
24 ISSUE_CHILD: Symbol('ISSUE_CHILD'),
23 PARTY_SET_POOL_SIZE: Symbol('PARTY_SET_POOL_SIZE'),
25 PARTY_SET_POOL_SIZE: Symbol('PARTY_SET_POOL_SIZE'),
24 INVITE_COHORT: Symbol('INVITE_COHORT'),
26 INVITE_COHORT: Symbol('INVITE_COHORT'),
src/lib/router.js+4
@@ -2,6 +2,8 @@ import Disclaimer from 'views/Disclaimer';
2import Login from 'views/Login.js';
2import Login from 'views/Login.js';
3import Other from 'views/Login/Other.js';
3import Other from 'views/Login/Other.js';
4import Senate from 'views/Senate';
4import Senate from 'views/Senate';
5+import Documents from 'views/Senate/Documents';
6+import Upgrades from 'views/Senate/Upgrades';
5import CreateGalaxy from 'views/CreateGalaxy';
7import CreateGalaxy from 'views/CreateGalaxy';
6import IssueChild from 'views/IssueChild';
8import IssueChild from 'views/IssueChild';
7import StarRelease from 'views/Release';
9import StarRelease from 'views/Release';
@@ -35,6 +37,8 @@ export const ROUTES = {
35 [ROUTE_NAMES.CREATE_GALAXY]: CreateGalaxy,
37 [ROUTE_NAMES.CREATE_GALAXY]: CreateGalaxy,
36 [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
38 [ROUTE_NAMES.ISSUE_CHILD]: IssueChild,
37 [ROUTE_NAMES.SENATE]: Senate,
39 [ROUTE_NAMES.SENATE]: Senate,
40+ [ROUTE_NAMES.DOCUMENTS]: Documents,
41+ [ROUTE_NAMES.UPGRADES]: Upgrades,
38 // TODO: nest this route under the Party.js router
42 // TODO: nest this route under the Party.js router
39 [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
43 [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
40 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
44 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
src/views/Senate.js+25−216
@@ -1,231 +1,40 @@
1−import React, { useCallback, useMemo, useState } from 'react';
1+import React, { useCallback, useState } from 'react';
2−import { Grid, Text, P, LinkButton } from 'indigo-react';
2+import { Grid } from 'indigo-react';
3−import * as azimuth from 'azimuth-js';
3+import { azimuth } from 'azimuth-js';
4
4
5−import { useNetwork } from 'store/network';
6−import { usePointCursor } from 'store/pointCursor';
7−
8−import * as need from 'lib/need';
9−import useEthereumTransaction from 'lib/useEthereumTransaction';
10−import { GAS_LIMITS } from 'lib/constants';
11−import { useLocalRouter } from 'lib/LocalRouter';
12−
13−import ViewHeader from 'components/ViewHeader';
14−import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15import View from 'components/View';
5import View from 'components/View';
16−import BridgeForm from 'form/BridgeForm';
6+import ViewHeader from 'components/ViewHeader';
17−import convertToInt from 'lib/convertToInt';
7+import { ForwardButton } from 'components/Buttons';
18−
19−function useCastDocumentVote() {
20− const { contracts } = useNetwork();
21− const { pointCursor } = usePointCursor();
22−
23− const _contracts = need.contracts(contracts);
24− const _point = need.point(pointCursor);
25−
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− );
41−}
42−
43−function renderDocumentHash(doc) {
44− return doc.slice(0, 34) + '\n ' + doc.slice(34);
45−}
46−
47−export default function Senate() {
48− const { pop } = useLocalRouter();
49− const { contracts } = useNetwork();
50− const { pointCursor } = usePointCursor();
51−
52− const _contracts = need.contracts(contracts);
53− const _point = convertToInt(need.point(pointCursor), 10);
54−
55− const { construct, bind } = useCastDocumentVote();
56−
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({});
63−
64− //TODO update every second?
65− const now = Math.round(new Date().getTime() / 1000);
66−
67− useMemo(async () => {
68− let proposals = await azimuth.polls.getDocumentProposals(_contracts);
69− let majorities = await azimuth.polls.getDocumentMajorities(_contracts);
70− let polls = {};
71− for (let doc of proposals) {
72− let poll = await azimuth.polls.getDocumentPoll(_contracts, doc);
73− poll.endTime = convertToInt(poll.start) + convertToInt(poll.duration);
74− poll.hasVoted = await azimuth.polls.hasVotedOnDocumentPoll(
75− _contracts,
76− _point,
77− doc
78− );
79− polls[doc] = poll;
80− }
81− setMajorities(majorities);
82− setProposals(proposals);
83− setPolls(polls);
84− setLoading(false);
85− return;
86− }, [_contracts, _point]);
87−
88− // eslint-disable-next-line
89− const [open, closed] = useMemo(() => {
90− let open = [];
91− let closed = [];
92− if (loading) return [open, closed];
93− for (let doc of proposals) {
94− if (now > polls[doc].endTime) {
95− closed.push(doc);
96− } else {
97− open.push(doc);
98− }
99− }
100− return [open, closed];
101− }, [loading, proposals, polls, now]);
102
8
103− const doVote = useCallback(
9+import { useLocalRouter } from 'lib/LocalRouter';
104− (hash, accept) => {
105− construct(hash, accept);
106− setVotingOn({ hash, accept });
107− },
108− [construct, setVotingOn]
109− );
110
10
111− const didVote = useCallback(() => {
11+export default function Point() {
112− polls[votingOn].hasVoted = true;
12+ const { pop, push, names } = useLocalRouter();
113− setPolls(polls);
114− setVotingOn({});
115− }, [polls, setPolls, votingOn]);
116
13
117− const majorityList = useMemo(() => {
14+ //TODO if we moved the poll fetching logic into a cache/store,
118− return majorities.map(doc => {
15+ // we could be cute here and display "x ongoing" under the buttons.
119− return (
120− <Grid.Item full className="mv2" as={P}>
121− <a
122− target="_blank"
123− rel="noopener noreferrer"
124− href={`https://raw.githubusercontent.com/urbit/azimuth/master/proposals/${doc}.txt`}
125− class="mb4 mono wrap pre">
126− {renderDocumentHash(doc)}↗
127− </a>
128− </Grid.Item>
129− );
130− });
131− }, [majorities]);
132
16
133− const openList = useMemo(() => {
17+ const goDocuments = useCallback(() => push(names.DOCUMENTS), [push, names]);
134− return open.map(doc => {
135− const hasVoted = polls[doc].hasVoted;
136− return (
137− <>
138− <Grid.Item full className="mv2" as={Text}>
139− <P className="m0 mono wrap pre">{renderDocumentHash(doc)}</P>
140− <P className="mt0">
141− {hasVoted ? (
142− 'Your vote has been cast.'
143− ) : (
144− <>
145− <LinkButton onClick={() => doVote(doc, true)}>
146− {votingOn.hash === doc && votingOn.accept ? (
147− <b>support</b>
148− ) : (
149− <>support</>
150− )}
151− </LinkButton>
152− {' / '}
153− <LinkButton onClick={() => doVote(doc, false)}>
154− {votingOn.hash === doc && !votingOn.accept ? (
155− <b>reject</b>
156− ) : (
157− <>reject</>
158− )}
159− </LinkButton>
160− </>
161− )}
162− </P>
163− </Grid.Item>
164− <Grid.Divider />
165− </>
166− );
167− });
168− }, [open, polls, votingOn, doVote]);
169
18
170− const onDocumentChange = useCallback(({ valid, values }) => {
19+ const goUpgrades = useCallback(() => push(names.UPGRADES), [push, names]);
171− // if (values.document) {
172− // const hash = '0x' + keccak256(values.document.trim()).toString('hex');
173− // setDocumentHash(hash);
174− // } else {
175− // setDocumentHash('0x...');
176− // }
177− }, []);
178
20
179 return (
21 return (
180 <View pop={pop} inset>
22 <View pop={pop} inset>
181− <Grid>
182− <Grid.Item full as={ViewHeader}>
23+ <Grid.Item full as={ViewHeader}>
24+ Senate: Proposals
25+ </Grid.Item>
26+ <Grid className="pt2">
27+ <Grid.Item
28+ full
29+ as={ForwardButton}
30+ className="mt1"
31+ onClick={goDocuments}>
183− Senate: Document Proposals
32+ Document Proposals
184 </Grid.Item>
33 </Grid.Item>
185−
34+ <Grid.Divider />
186− <Grid.Item full as={Grid}>
35+ <Grid.Item full as={ForwardButton} className="mt1" onClick={goUpgrades}>
187− <Grid.Item full as={P}>
36+ Upgrade Proposals
188− Accepted documents:
189− </Grid.Item>
190− {majorityList}
191 </Grid.Item>
37 </Grid.Item>
192−
193− <Grid.Divider className="mv4" />
194−
195− <Grid.Item full as={Grid}>
196− <Grid.Item full as={P}>
197− Open document polls:
198− </Grid.Item>
199− {openList}
200− </Grid.Item>
201−
202− <BridgeForm onValues={onDocumentChange}>
203− {({ onSubmit, values }) => (
204− // <>
205− <Grid.Item
206− full
207− as={InlineEthereumTransaction}
208− {...bind}
209− onReturn={didVote}
210− />
211−
212− // <Grid.Divider />
213− //
214− // <Grid.Item
215− // full
216− // as={Input}
217− // type="textarea"
218− // placeholder="Input some text to find its hash..."
219− // name="document"
220− // className="mt4"
221− // />
222− //
223− // <Grid.Item full as={CopiableAddress}>
224− // {documentHash}
225− // </Grid.Item>
226− // </>
227− )}
228− </BridgeForm>
229 </Grid>
38 </Grid>
230 </View>
39 </View>
231 );
40 );
src/views/Senate/Documents.jsnew file+215
@@ -0,0 +1,215 @@
1+import React, { useCallback, useMemo, useEffect, useState } from 'react';
2+import { Grid, Text, P, LinkButton } from 'indigo-react';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from 'store/network';
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import * as need from 'lib/need';
9+import useEthereumTransaction from 'lib/useEthereumTransaction';
10+import { GAS_LIMITS } from 'lib/constants';
11+import { useLocalRouter } from 'lib/LocalRouter';
12+
13+import ViewHeader from 'components/ViewHeader';
14+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15+import View from 'components/View';
16+import BridgeForm from 'form/BridgeForm';
17+import convertToInt from 'lib/convertToInt';
18+
19+function useCastDocumentVote() {
20+ const { contracts } = useNetwork();
21+ const { pointCursor } = usePointCursor();
22+
23+ const _contracts = need.contracts(contracts);
24+ const _point = need.point(pointCursor);
25+
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+ );
41+}
42+
43+function renderDocumentHash(doc) {
44+ return doc.slice(0, 34) + '\n ' + doc.slice(34);
45+}
46+
47+export default function Documents() {
48+ const { pop } = useLocalRouter();
49+ const { contracts } = useNetwork();
50+ const { pointCursor } = usePointCursor();
51+
52+ const _contracts = need.contracts(contracts);
53+ const _point = convertToInt(need.point(pointCursor), 10);
54+
55+ const { construct, reset, unconstruct, bind } = useCastDocumentVote();
56+
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({});
63+
64+ //TODO update every second?
65+ const now = Math.round(new Date().getTime() / 1000);
66+
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]);
88+
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]);
103+
104+ const doVote = useCallback(
105+ (hash, accept) => {
106+ construct(hash, accept);
107+ setVotingOn({ hash, accept });
108+ },
109+ [construct]
110+ );
111+
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]);
121+
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]);
137+
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]);
174+
175+ return (
176+ <View pop={pop} inset>
177+ <Grid>
178+ <Grid.Item full as={ViewHeader}>
179+ Senate: Document Proposals
180+ </Grid.Item>
181+
182+ <Grid.Item full as={Grid}>
183+ <Grid.Item full as={P}>
184+ Accepted documents:
185+ </Grid.Item>
186+ {majorityList}
187+ </Grid.Item>
188+
189+ {openList.length > 0 && (
190+ <>
191+ <Grid.Divider className="mv4" />
192+
193+ <Grid.Item full as={Grid}>
194+ <Grid.Item full as={P}>
195+ Open document polls:
196+ </Grid.Item>
197+ {openList}
198+ </Grid.Item>
199+
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+ );
215+}
src/views/Senate/Upgrades.jsnew file+199
@@ -0,0 +1,199 @@
1+import React, { useCallback, useMemo, useEffect, useState } from 'react';
2+import { Grid, Text, P, LinkButton } from 'indigo-react';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from 'store/network';
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import * as need from 'lib/need';
9+import useEthereumTransaction from 'lib/useEthereumTransaction';
10+import { GAS_LIMITS } from 'lib/constants';
11+import { useLocalRouter } from 'lib/LocalRouter';
12+
13+import ViewHeader from 'components/ViewHeader';
14+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
15+import View from 'components/View';
16+import BridgeForm from 'form/BridgeForm';
17+import convertToInt from 'lib/convertToInt';
18+
19+function useCastUpgradeVote() {
20+ const { contracts } = useNetwork();
21+ const { pointCursor } = usePointCursor();
22+
23+ const _contracts = need.contracts(contracts);
24+ const _point = need.point(pointCursor);
25+
26+ return useEthereumTransaction(
27+ useCallback(
28+ (address, accept) => {
29+ return azimuth.ecliptic.castUpgradeVote(
30+ _contracts,
31+ _point,
32+ address,
33+ accept
34+ );
35+ },
36+ [_contracts, _point]
37+ ),
38+ useCallback(() => null, []), //TODO update polls[address].hasVoted ?
39+ GAS_LIMITS.DEFAULT //TODO update once we have a decent sample size
40+ );
41+}
42+
43+export default function Upgrades() {
44+ const { pop } = useLocalRouter();
45+ const { contracts } = useNetwork();
46+ const { pointCursor } = usePointCursor();
47+
48+ const _contracts = need.contracts(contracts);
49+ const _point = convertToInt(need.point(pointCursor), 10);
50+
51+ const { construct, reset, unconstruct, bind } = useCastUpgradeVote();
52+
53+ const [proposals, setProposals] = useState([]);
54+ const [polls, setPolls] = useState({});
55+ const [loading, setLoading] = useState(true);
56+ const [votingOn, setVotingOn] = useState({});
57+
58+ //TODO update every second?
59+ const now = Math.round(new Date().getTime() / 1000);
60+
61+ useEffect(() => {
62+ (async () => {
63+ let proposals = await azimuth.polls.getUpgradeProposals(_contracts);
64+ let polls = {};
65+ for (let address of proposals) {
66+ let poll = await azimuth.polls.getUpgradePoll(_contracts, address);
67+ poll.endTime = convertToInt(poll.start) + convertToInt(poll.duration);
68+ poll.hasVoted = await azimuth.polls.hasVotedOnUpgradePoll(
69+ _contracts,
70+ _point,
71+ address
72+ );
73+ polls[address] = poll;
74+ }
75+ setProposals(proposals);
76+ setPolls(polls);
77+ setLoading(false);
78+ })();
79+ }, [_contracts, _point]);
80+
81+ // eslint-disable-next-line
82+ const [open, closed] = useMemo(() => {
83+ let open = [];
84+ let closed = [];
85+ if (loading) return [open, closed];
86+ for (let address of proposals) {
87+ if (now > polls[address].endTime) {
88+ closed.push(address);
89+ } else {
90+ open.push(address);
91+ }
92+ }
93+ return [open, closed];
94+ }, [loading, proposals, polls, now]);
95+
96+ const doVote = useCallback(
97+ (address, accept) => {
98+ construct(address, accept);
99+ setVotingOn({ address, accept });
100+ },
101+ [construct]
102+ );
103+
104+ const didVote = useCallback(() => {
105+ reset();
106+ unconstruct();
107+ setPolls(polls => {
108+ polls[votingOn.address].hasVoted = true;
109+ return polls;
110+ });
111+ setVotingOn({});
112+ }, [votingOn, reset, unconstruct]);
113+
114+ const openList = useMemo(() => {
115+ return open.map(address => {
116+ const hasVoted = polls[address].hasVoted;
117+ return (
118+ <>
119+ <Grid.Item full className="mv2" as={Text}>
120+ <a
121+ target="_blank"
122+ rel="noopener noreferrer"
123+ href={`https://etherscan.io/address/${address}#code`}
124+ class="mb4 mono wrap pre">
125+ {address}↗
126+ </a>
127+ <P className="mt0">
128+ {hasVoted ? (
129+ 'Your vote has been cast.'
130+ ) : (
131+ <>
132+ <LinkButton onClick={() => doVote(address, true)}>
133+ {votingOn.address === address && votingOn.accept ? (
134+ <b>support</b>
135+ ) : (
136+ <>support</>
137+ )}
138+ </LinkButton>
139+ {' / '}
140+ <LinkButton onClick={() => doVote(address, false)}>
141+ {votingOn.address === address && !votingOn.accept ? (
142+ <b>reject</b>
143+ ) : (
144+ <>reject</>
145+ )}
146+ </LinkButton>
147+ </>
148+ )}
149+ </P>
150+ </Grid.Item>
151+ <Grid.Divider />
152+ </>
153+ );
154+ });
155+ }, [open, polls, votingOn, doVote]);
156+
157+ return (
158+ <View pop={pop} inset>
159+ <Grid>
160+ <Grid.Item full as={ViewHeader}>
161+ Senate: Upgrade Proposals
162+ </Grid.Item>
163+
164+ <Grid.Item full as={Grid}>
165+ <Grid.Item full as={P}>
166+ Current Ecliptic:
167+ </Grid.Item>
168+ <Grid.Item full className="mv2 mb4 mono wrap pre" as={P}>
169+ {_contracts.ecliptic.address}
170+ </Grid.Item>
171+ </Grid.Item>
172+
173+ {openList.length > 0 && (
174+ <>
175+ <Grid.Divider className="mv4" />
176+
177+ <Grid.Item full as={Grid}>
178+ <Grid.Item full as={P}>
179+ Open upgrade polls:
180+ </Grid.Item>
181+ {openList}
182+ </Grid.Item>
183+
184+ <BridgeForm>
185+ {({ onSubmit, values }) => (
186+ <Grid.Item
187+ full
188+ as={InlineEthereumTransaction}
189+ {...bind}
190+ onReturn={didVote}
191+ />
192+ )}
193+ </BridgeForm>
194+ </>
195+ )}
196+ </Grid>
197+ </View>
198+ );
199+}