@@ -1,186 +1,219 @@
11import React from 'react';
22import { Just, Nothing } from 'folktale/maybe';
3−import { H1, H2, P, Grid } from 'indigo-react';
4−
5−import { ROUTE_NAMES } from 'lib/routeNames';
6−import { isZeroAddress } from 'lib/wallet';
7−import useIsEclipticOwner from 'lib/useIsEclipticOwner';
8−import { useSyncKnownPoints } from 'lib/useSyncPoints';
3+import { Grid, H5, H1, HelpText, LinkButton, Flex } from 'indigo-react';
94
105import { useHistory } from 'store/history';
116import { useWallet } from 'store/wallet';
127import { usePointCache } from 'store/pointCache';
8+import { usePointCursor } from 'store/pointCursor';
9+
10+import * as need from 'lib/need';
11+import { ROUTE_NAMES } from 'lib/routeNames';
12+import { isZeroAddress, abbreviateAddress } from 'lib/wallet';
13+import useIsEclipticOwner from 'lib/useIsEclipticOwner';
14+import { useSyncKnownPoints } from 'lib/useSyncPoints';
1315
1416import View from 'components/View';
15−import { ForwardButton } from 'components/Buttons';
16−import useLifecycle from 'lib/useLifecycle';
17+import FooterButton from 'components/FooterButton';
18+import Blinky from 'components/Blinky';
19+import Passport from 'components/Passport';
20+import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
21+
22+const getFromMaybe = (obj, key, defaultValue) =>
23+ obj.matchWith({
24+ Nothing: () => defaultValue,
25+ Just: p =>
26+ p.value.matchWith({
27+ Ok: r => r.value[key],
28+ Error: e => defaultValue,
29+ }),
30+ });
1731
18−//TODO new component
19−import { usePointCursor } from 'store/pointCursor';
20−import * as ob from 'urbit-ob';
21−const PointList = function(props) {
32+const hasTransferProxy = (details, point) =>
33+ !isZeroAddress(details.transferProxy);
34+
35+const PointList = function({ points, className, actions, ...rest }) {
2236 const { setPointCursor } = usePointCursor();
2337 const history = useHistory();
24− const { points, loading } = props;
25−
26− return points.length === 0 ? (
27− <p>{loading ? 'Loading...' : 'No points to display'}</p>
28− ) : (
29− <>
30− {points.map(point => (
31− <P
32− key={point}
33− onClick={() => {
34− setPointCursor(Just(point));
35− history.push(ROUTE_NAMES.POINT);
36− }}>
37− {ob.patp(point)}
38− </P>
38+
39+ return (
40+ <Grid gap={3} className={className}>
41+ {points.map((point, i) => (
42+ <Grid.Item half={(i % 2) + 1} key={point}>
43+ <Flex col>
44+ <Passport.Mini
45+ point={point}
46+ className="clickable"
47+ onClick={() => {
48+ setPointCursor(Just(point));
49+ history.push(history.names.POINT);
50+ }}
51+ {...rest}
52+ />
53+ {actions && (
54+ <Flex.Item className="mt2">{actions(point, i)}</Flex.Item>
55+ )}
56+ </Flex>
57+ </Grid.Item>
3958 ))}
40− </>
59+ </Grid>
4160 );
4261};
4362
44−const matchArray = obj =>
45− obj.matchWith({ Nothing: () => [], Just: p => p.value });
46−
47−const hasTransferProxy = (details, point) =>
48− !isZeroAddress(details.transferProxy);
63+function ActionButtons({ actions = [] }) {
64+ return (
65+ <Flex row>
66+ {actions.map(action => (
67+ <Flex.Item
68+ key={action.text}
69+ as={LinkButton}
70+ className="mr3"
71+ onClick={action.onClick}>
72+ {action.text}
73+ </Flex.Item>
74+ ))}
75+ </Flex>
76+ );
77+}
4978
5079export default function Points() {
5180 const { wallet } = useWallet();
5281 const history = useHistory();
53− const {
54− getControlledPoints,
55− syncControlledPoints,
56− getDetails,
57− } = usePointCache();
58− const {
59− ownedPoints,
60− incomingPoints,
61− managingPoints,
62− votingPoints,
63− spawningPoints,
64− } = getControlledPoints(wallet);
82+ const { setPointCursor } = usePointCursor();
83+ const { controlledPoints, getDetails } = usePointCache();
6584 const isEclipticOwner = useIsEclipticOwner();
66−
67− const owned = matchArray(ownedPoints);
68− const incoming = matchArray(incomingPoints);
69− const managing = matchArray(managingPoints);
70− const voting = matchArray(votingPoints);
71− const spawning = matchArray(spawningPoints);
72−
73− // sync controlled points on mount
74− useLifecycle(() => {
75− syncControlledPoints();
76− });
77−
78− // sync display details for known points
79− useSyncKnownPoints([
80− ...owned,
81− ...incoming,
82− ...managing,
83− ...voting,
84− ...spawning,
85− ]);
86−
87− const loading = Nothing.hasInstance(ownedPoints);
88−
89− const outgoing = ownedPoints.filter(point =>
85+ const [
86+ rejectedPoints,
87+ addRejectedPoint,
88+ ] = useRejectedIncomingPointTransfers();
89+
90+ const address = need.addressFromWallet(wallet);
91+
92+ const loading = Nothing.hasInstance(controlledPoints);
93+
94+ const ownedPoints = getFromMaybe(controlledPoints, 'ownedPoints', []);
95+ const incomingPoints = getFromMaybe(
96+ controlledPoints,
97+ 'incomingPoints',
98+ []
99+ ).filter(point => !rejectedPoints.includes(point));
100+ const managingPoints = getFromMaybe(controlledPoints, 'managingPoints', []);
101+ const votingPoints = getFromMaybe(controlledPoints, 'votingPoints', []);
102+ const spawningPoints = getFromMaybe(controlledPoints, 'spawningPoints', []);
103+ const outgoingPoints = ownedPoints.filter(point =>
90104 getDetails(point).matchWith({
91105 Nothing: () => false,
92106 Just: p => hasTransferProxy(p.value),
93107 })
94108 );
95109
110+ const multipassPoints = [
111+ ...ownedPoints,
112+ ...managingPoints,
113+ ...votingPoints,
114+ ...spawningPoints,
115+ ];
116+
117+ // sync display details for known points
118+ useSyncKnownPoints([
119+ ...ownedPoints,
120+ ...incomingPoints,
121+ ...managingPoints,
122+ ...votingPoints,
123+ ...spawningPoints,
124+ ]);
125+
96126 return (
97127 <View>
98− <H1>Points</H1>
99−
100− <P>
101− A point is an identity on the Ethereum blockchain. Points declare keys
102− for ships on the Arvo network.
103− </P>
104−
105− <Grid>
106− <Grid.Divider />
107− <Grid.Item full>
108− <ForwardButton
109− detail="View a point on Azimuth"
110− onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
111− View a point
112− </ForwardButton>
128+ <Grid className="mt6">
129+ <Grid.Item full as={H1} className="f6 mono gray4 mb4">
130+ {abbreviateAddress(address)}
113131 </Grid.Item>
114− <Grid.Divider />
115− {isEclipticOwner && (
116− <>
117− <Grid.Item full>
118− <ForwardButton
119− detail="You have the authority to create a new Galaxy."
120− onClick={() => history.push(ROUTE_NAMES.CREATE_GALAXY)}>
121− Create a galaxy
122− </ForwardButton>
132+
133+ {loading && (
134+ <Grid.Item full as={HelpText} className="mt8 t-center">
135+ <Blinky /> Loading...
136+ </Grid.Item>
137+ )}
138+
139+ {incomingPoints.length > 0 && (
140+ <Grid.Item full as={Grid} gap={1} className="mb6">
141+ <Grid.Item full as={H5}>
142+ Incoming Transfers
123143 </Grid.Item>
124− <Grid.Divider />
125− </>
144+ <Grid.Item
145+ full
146+ as={PointList}
147+ points={incomingPoints}
148+ actions={(point, i) => (
149+ <ActionButtons
150+ actions={[
151+ {
152+ text: 'Accept',
153+ onClick: () => {
154+ setPointCursor(Just(point));
155+ history.push(history.names.ACCEPT_TRANSFER);
156+ },
157+ },
158+ {
159+ text: 'Reject',
160+ onClick: () => {
161+ addRejectedPoint(point);
162+ },
163+ },
164+ ]}
165+ />
166+ )}
167+ inverted
168+ />
169+ </Grid.Item>
170+ )}
171+
172+ {outgoingPoints.length > 0 && (
173+ <Grid.Item full as={Grid} gap={1} className="mb6">
174+ <Grid.Item full as={H5}>
175+ Outgoing Transfers
176+ </Grid.Item>
177+ <Grid.Item
178+ full
179+ as={PointList}
180+ points={outgoingPoints}
181+ actions={(point, i) => (
182+ <ActionButtons
183+ actions={[
184+ {
185+ text: 'Cancel',
186+ onClick: () => {
187+ setPointCursor(Just(point));
188+ // TODO: deep linking to fix this duplicate route
189+ history.push(history.names.CANCEL_TRANSFER);
190+ },
191+ },
192+ ]}
193+ />
194+ )}
195+ inverted
196+ />
197+ </Grid.Item>
126198 )}
127− </Grid>
128199
129− {incoming.length > 0 && (
130− <>
131− <H2>Incoming Transfers</H2>
132− <P>
133− You do not own these points until you accept the incoming transfer.
134− You may reject any incoming transfers.
135− </P>
136− <PointList routeHandler={history.push} points={incoming} />
137− </>
138− )}
139−
140− {outgoing.length > 0 && (
141− <>
142− <H2>Outgoing Transfers</H2>
143− <P>
144− You own these points until the recipient accepts the incoming
145− transfer. You may cancel the transfer until accepted.
146− </P>
147− <PointList routeHandler={history.push} points={outgoing} />
148− </>
149− )}
150−
151− <H2>Your Points</H2>
152−
153− <PointList routeHandler={history.push} points={owned} loading={loading} />
154−
155− {managing.length > 0 && (
156− <>
157− <H2>You Are a Management Proxy For</H2>
158− <P>
159− You can configure or set network keys and conduct sponsorship
160− related operations for these points.
161− </P>
162− <PointList routeHandler={history.push} points={managing} />
163− </>
164− )}
165−
166− {voting.length > 0 && (
167− <>
168− <H2>You Are a Voting Proxy For</H2>
169− <P>
170− Since you are part of the Galactic Senate, you can cast votes on new
171− Azimuth proposals on behalf of these points.
172− </P>
173− <PointList routeHandler={history.push} points={voting} />
174− </>
175− )}
176−
177− {spawning.length > 0 && (
178− <>
179− <H2>You Are a Spawn Proxy For</H2>
180− <P>You can create new child points under these points.</P>
181− <PointList routeHandler={history.push} points={spawning} />
182− </>
183− )}
200+ {multipassPoints.length > 0 && (
201+ <Grid.Item full as={Grid} gap={1}>
202+ <Grid.Item full as={H5}>
203+ Multipasses
204+ </Grid.Item>
205+ <Grid.Item full as={PointList} points={multipassPoints} />
206+ </Grid.Item>
207+ )}
208+
209+ {isEclipticOwner && (
210+ <FooterButton
211+ detail="You have the authority to create a new Galaxy."
212+ onClick={() => history.push(ROUTE_NAMES.CREATE_GALAXY)}>
213+ Create a galaxy
214+ </FooterButton>
215+ )}
216+ </Grid>
184217 </View>
185218 );
186219}