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