on efed151 help sign in
bridge/src/views/Points.js
351 lines 10 kB history raw
1import React, { useCallback, useEffect, useMemo } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, H5, H1, HelpText, LinkButton, Flex } from 'indigo-react';
4import { get } from 'lodash';
6import { useHistory } from 'store/history';
7import { useWallet } from 'store/wallet';
8import { usePointCache } from 'store/pointCache';
9import { usePointCursor } from 'store/pointCursor';
11import * as need from 'lib/need';
12import { isZeroAddress, abbreviateAddress } from 'lib/wallet';
13import useIsEclipticOwner from 'lib/useIsEclipticOwner';
14import { useSyncKnownPoints, useSyncOwnedPoints } from 'lib/useSyncPoints';
15import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
16import pluralize from 'lib/pluralize';
18import View from 'components/View';
19import Blinky from 'components/Blinky';
20import Passport from 'components/Passport';
21import Footer from 'components/Footer';
22import { ForwardButton } from 'components/Buttons';
23import CopiableAddress from 'components/CopiableAddress';
24import NavHeader from 'components/NavHeader';
26const maybeGetResult = (obj, key, defaultValue) =>
27 obj.matchWith({
28 Nothing: () => defaultValue,
29 Just: p =>
30 p.value.matchWith({
31 Ok: r => get(r.value, key, defaultValue),
32 Error: e => defaultValue,
33 }),
34 });
36const hasTransferProxy = details => !isZeroAddress(details.transferProxy);
38const PointList = function({ points, className, actions, ...rest }) {
39 const { setPointCursor } = usePointCursor();
40 const { push, names } = useHistory();
42 return (
43 <Grid gap={3} className={className}>
44 {points.map((point, i) => (
45 <Grid.Item
46 key={point}
47 className={`full half-${(i % 2) + 1}-md half-${(i % 2) + 1}-lg`}>
48 <Flex col>
49 <Passport.Mini
50 point={point}
51 className="pointer"
52 onClick={() => {
53 setPointCursor(Just(point));
54 push(names.POINT);
55 }}
56 {...rest}
57 />
58 {actions && (
59 <Flex.Item className="mt2">{actions(point, i)}</Flex.Item>
60 )}
61 </Flex>
62 </Grid.Item>
63 ))}
64 </Grid>
65 );
66};
68function ActionButtons({ actions = [] }) {
69 return (
70 <Flex row>
71 {actions.map(action => (
72 <Flex.Item
73 key={action.text}
74 as={LinkButton}
75 className="mr3"
76 onClick={action.onClick}>
77 {action.text}
78 </Flex.Item>
79 ))}
80 </Flex>
81 );
84export default function Points() {
85 const { wallet } = useWallet();
86 const { pop, push, popAndPush, names } = useHistory();
87 const { setPointCursor } = usePointCursor();
88 const { controlledPoints, getDetails } = usePointCache();
89 const isEclipticOwner = useIsEclipticOwner();
90 const [
91 rejectedPoints,
92 addRejectedPoint,
93 ] = useRejectedIncomingPointTransfers();
95 const maybeOutgoingPoints = useMemo(
96 () =>
97 controlledPoints.chain(points =>
98 points.matchWith({
99 Error: () => Nothing(),
100 Ok: c => {
101 const points = c.value.ownedPoints.map(point =>
102 getDetails(point).chain(details =>
103 Just({ point: point, has: hasTransferProxy(details) })
104 )
105 );
106 // if we have details for every point,
107 // return the array of pending transfers.
108 if (points.every(p => Just.hasInstance(p))) {
109 const outgoing = points
110 .filter(p => p.value.has)
111 .map(p => p.value.point);
112 return Just(outgoing);
113 } else {
114 return Nothing();
115 }
116 },
117 })
118 ),
119 [getDetails, controlledPoints]
120 );
122 // if we can only interact with a single point, forget about the existence
123 // of this page and jump to the point page.
124 // if there are any pending transfers, incoming or outgoing, stay on this
125 // page, because those can only be completed/cancelled here.
126 useEffect(() => {
127 if (Nothing.hasInstance(maybeOutgoingPoints)) {
128 return;
129 }
130 controlledPoints.matchWith({
131 Nothing: () => null,
132 Just: r => {
133 r.value.matchWith({
134 Error: () => null,
135 Ok: c => {
136 let all = [
137 ...c.value.ownedPoints,
138 ...c.value.votingPoints,
139 ...c.value.managingPoints,
140 ...c.value.spawningPoints,
141 ];
142 const incoming = c.value.incomingPoints.filter(
143 p => !rejectedPoints.includes(p)
144 );
145 if (
146 all.length === 1 &&
147 incoming.length === 0 &&
148 maybeOutgoingPoints.value.length === 0
149 ) {
150 setPointCursor(Just(all[0]));
151 popAndPush(names.POINT);
152 }
153 },
154 });
155 },
156 });
157 }, [
158 controlledPoints,
159 rejectedPoints,
160 maybeOutgoingPoints,
161 setPointCursor,
162 popAndPush,
163 names,
164 ]);
166 const address = need.addressFromWallet(wallet);
168 const loading = Nothing.hasInstance(controlledPoints);
170 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
171 const incomingPoints = maybeGetResult(
172 controlledPoints,
173 'incomingPoints',
174 []
175 ).filter(point => !rejectedPoints.includes(point));
176 const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
177 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
178 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
179 const outgoingPoints = maybeOutgoingPoints.getOrElse([]);
181 const allPoints = [
182 ...ownedPoints.filter(p => !outgoingPoints.includes(p)),
183 ...managingPoints,
184 ...votingPoints,
185 ...spawningPoints,
186 ];
188 const displayEmptyState =
189 !loading && incomingPoints.length === 0 && allPoints.length === 0;
191 // sync display details for known points
192 useSyncKnownPoints([
193 ...ownedPoints,
194 ...incomingPoints,
195 ...managingPoints,
196 ...votingPoints,
197 ...spawningPoints,
198 ]);
200 useSyncOwnedPoints(ownedPoints);
202 const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
203 names.CREATE_GALAXY,
204 push,
205 ]);
207 const goViewPoint = useCallback(() => push(names.VIEW_POINT), [
208 names.VIEW_POINT,
209 push,
210 ]);
212 if (
213 loading ||
214 (allPoints.length === 1 &&
215 incomingPoints.length === 0 &&
216 outgoingPoints.length === 0)
217 ) {
218 return (
219 <View inset pop={pop}>
220 <Grid>
221 <Grid.Item full as={HelpText} className="mt8 t-center">
222 <Blinky /> Loading...
223 </Grid.Item>
224 </Grid>
225 </View>
226 );
227 }
229 return (
230 <View pop={pop} inset>
231 <NavHeader>
232 <CopiableAddress
233 text={address}
234 className="f6 mono gray4 mb4 us-none pointer">
235 {abbreviateAddress(address)}
236 </CopiableAddress>
237 </NavHeader>
238 <Grid>
239 {displayEmptyState && (
240 <Grid.Item full as={HelpText} className="mt8 t-center">
241 No points to display. This wallet is not the owner or proxy for any
242 points.
243 </Grid.Item>
244 )}
246 {incomingPoints.length > 0 && (
247 <Grid.Item full as={Grid} gap={1} className="mb6">
248 <Grid.Item full as={H5}>
249 {pluralize(
250 incomingPoints.length,
251 'Incoming Transfer',
252 'Incoming Transfers'
253 )}
254 </Grid.Item>
255 <Grid.Item
256 full
257 as={PointList}
258 points={incomingPoints}
259 actions={(point, i) => (
260 <ActionButtons
261 actions={[
262 {
263 text: 'Accept',
264 onClick: () => {
265 setPointCursor(Just(point));
266 push(names.ACCEPT_TRANSFER);
267 },
268 },
269 {
270 text: 'Reject',
271 onClick: () => {
272 addRejectedPoint(point);
273 },
274 },
275 ]}
276 />
277 )}
278 inverted
279 />
280 </Grid.Item>
281 )}
283 {outgoingPoints.length > 0 && (
284 <Grid.Item full as={Grid} gap={1} className="mb6">
285 <Grid.Item full as={H5}>
286 {pluralize(
287 outgoingPoints.length,
288 'Outgoing Transfer',
289 'Outgoing Transfers'
290 )}
291 </Grid.Item>
292 <Grid.Item
293 full
294 as={PointList}
295 points={outgoingPoints}
296 actions={(point, i) => (
297 <ActionButtons
298 actions={[
299 {
300 text: 'Cancel',
301 onClick: () => {
302 setPointCursor(Just(point));
303 // TODO: deep linking to fix this duplicate route
304 push(names.CANCEL_TRANSFER);
305 },
306 },
307 ]}
308 />
309 )}
310 inverted
311 />
312 </Grid.Item>
313 )}
315 {allPoints.length > 0 && (
316 <Grid.Item full as={Grid} gap={1}>
317 <Grid.Item full as={H5}>
318 {pluralize(allPoints.length, 'ID')}
319 </Grid.Item>
320 <Grid.Item full as={PointList} points={allPoints} />
321 </Grid.Item>
322 )}
324 <Footer>
325 <Grid>
326 <Grid.Divider />
327 {isEclipticOwner && (
328 <>
329 <Grid.Item
330 full
331 as={ForwardButton}
332 detail="You have the authority to create a new Galaxy."
333 onClick={goCreateGalaxy}>
334 Create a galaxy
335 </Grid.Item>
336 <Grid.Divider />
337 </>
338 )}
339 <Grid.Item
340 full
341 as={ForwardButton}
342 detail="View an ID"
343 onClick={goViewPoint}>
344 View a point
345 </Grid.Item>
346 </Grid>
347 </Footer>
348 </Grid>
349 </View>
350 );