on ca31bdf help sign in
bridge/src/views/Point/Residents.js
272 lines 7.0 kB history raw
1import React, { useState, useEffect, useCallback, useMemo } from 'react';
2import cn from 'classnames';
3import { Grid, H4, HelpText } from 'indigo-react';
4import { Just, Nothing } from 'folktale/maybe';
5import * as ob from 'urbit-ob';
7import View from 'components/View';
8import Tabs from 'components/Tabs';
9import Crumbs from 'components/Crumbs';
10import Sigil from 'components/Sigil';
11import Blinky from 'components/Blinky';
12import NavHeader from 'components/NavHeader';
13import { ForwardButton } from 'components/Buttons';
15import { useLocalRouter } from 'lib/LocalRouter';
16import useCurrentPointName from 'lib/useCurrentPointName';
17import useCurrentPermissions from 'lib/useCurrentPermissions';
18import * as need from 'lib/need';
20import { usePointCursor } from 'store/pointCursor';
21import { usePointCache } from 'store/pointCache';
22import Footer from 'components/Footer';
23import { isGalaxy } from 'lib/utils/point';
25const NAMES = {
26 REQUESTS: 'REQUESTS',
27 ALL: 'ALL',
28};
30const OPTIONS = [
31 { text: 'Requests', value: NAMES.REQUESTS },
32 { text: 'All', value: NAMES.ALL },
33];
35const VIEWS = {
36 [NAMES.REQUESTS]: Tab,
37 [NAMES.ALL]: Tab,
38};
40const PAGE_SIZE = 10;
42function Tab({ className, points, onAccept, onDecline, page, setPage }) {
43 const start = page * PAGE_SIZE;
44 const end = page * PAGE_SIZE + PAGE_SIZE;
45 const pointsCount = points.map(ps => ps.length).getOrElse(0);
46 const maxPage = Math.ceil(pointsCount / PAGE_SIZE) - 1;
47 const hasNext = page < maxPage;
48 const hasPrev = page > 0;
49 const _points = points.map(ps => ps.slice(start, end)).getOrElse([]);
51 const onNext = useCallback(() => {
52 setPage(p => p + 1);
53 }, [setPage]);
55 const onPrev = useCallback(() => {
56 setPage(p => p - 1);
57 }, [setPage]);
59 if (Nothing.hasInstance(points)) {
60 return <Grid className={className}></Grid>;
61 }
63 return (
64 <Grid className={cn('mt2', className)}>
65 {_points.length === 0 && (
66 <Grid.Item full as={HelpText} className="mt8 t-center">
67 {Just.hasInstance(points) ? (
68 'No points to display'
69 ) : (
70 <>
71 <Blinky /> Loading...
72 </>
73 )}
74 </Grid.Item>
75 )}
77 {_points.map(point => (
78 <Resident
79 key={point}
80 point={point}
81 onAccept={onAccept}
82 onDecline={onDecline}
83 />
84 ))}
85 <Grid.Item as={Footer}>
86 <Grid className="pb9">
87 {hasPrev && (
88 <Grid.Item
89 className="pointer underline mt4"
90 fourth={1}
91 onClick={onPrev}>
92 {'<-'}
93 </Grid.Item>
94 )}
95 {maxPage > 0 && (
96 <Grid.Item className="mt4 t-center gray3" cols={[4, 10]}>
97 <span className="black">Page {page + 1}</span> of {maxPage + 1}
98 </Grid.Item>
99 )}
100 {hasNext && (
101 <Grid.Item
102 className="pointer underline t-right mt4"
103 fourth={4}
104 onClick={onNext}>
105 {'->'}
106 </Grid.Item>
107 )}
108 </Grid>
109 </Grid.Item>
110 </Grid>
111 );
114function Resident({ point, onAccept, onDecline }) {
115 const patp = ob.patp(point);
116 const sigilSize = 50;
118 const isRequest = onAccept && onDecline;
120 return (
121 <>
122 <Grid.Item className="flex-row justify-center align-center" cols={[1, 3]}>
123 <div
124 style={{
125 display: 'inline-block',
126 height: `${sigilSize}px`,
127 width: `${sigilSize}px`,
128 }}>
129 <Sigil patp={patp} size={25} colors={['#FFFFFF', '#000000']} />
130 </div>
131 </Grid.Item>
132 <Grid.Item className="flex-row align-center mono" cols={[3, 7]}>
133 {patp}{' '}
134 </Grid.Item>
135 {isRequest && (
136 <>
137 <Grid.Item
138 className="flex-row-r align-center underline green3 pointer"
139 cols={[7, 10]}
140 onClick={() => onAccept(point)}>
141 Accept
142 </Grid.Item>
143 <Grid.Item
144 className="flex-row-r align-center underline red4 pointer"
145 cols={[10, 13]}
146 onClick={() => onDecline(point)}>
147 Decline
148 </Grid.Item>
149 </>
150 )}
151 <Grid.Divider />
152 </>
153 );
156export default function Residents() {
157 const { pop, push, names } = useLocalRouter();
158 const name = useCurrentPointName();
160 const { pointCursor } = usePointCursor();
161 const point = need.point(pointCursor);
162 const { getResidents, syncResidents } = usePointCache();
164 const goBack = useCallback(() => pop(), [pop]);
165 useEffect(() => {
166 syncResidents(point);
167 }, [syncResidents, point]);
169 const { residents, requests } = getResidents(point);
171 const [currentTab, _setCurrentTab] = useState(NAMES.ALL);
172 const [page, setPage] = useState(0);
174 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
176 const goIssuePoint = useCallback(() => push(names.ISSUE_CHILD), [
177 names.ISSUE_CHILD,
178 push,
179 ]);
181 const setCurrentTab = useCallback(
182 tab => {
183 setPage(0);
184 _setCurrentTab(tab);
185 },
186 [setPage, _setCurrentTab]
187 );
189 const isRequests = useMemo(() => currentTab === NAMES.REQUESTS, [currentTab]);
191 const _onAccept = useCallback(
192 adoptee =>
193 push(names.ADOPT, {
194 adoptee,
195 denied: false,
196 }),
197 [push, names]
198 );
199 const onAccept = isRequests && _onAccept;
201 const _onDecline = useCallback(
202 adoptee =>
203 push(names.ADOPT, {
204 adoptee,
205 denied: true,
206 }),
207 [push, names]
208 );
209 const onDecline = isRequests && _onDecline;
211 const points = isRequests ? requests : residents;
212 const { canSpawn, canVote, isStar } = useCurrentPermissions();
214 const senateButton = (() => {
215 if (!isGalaxy(point)) {
216 return null;
217 }
218 return (
219 <>
220 <Grid.Item
221 full
222 as={ForwardButton}
223 disabled={!canVote}
224 onClick={goSenate}>
225 Senate
226 </Grid.Item>
227 <Grid.Divider />
228 </>
229 );
230 })();
232 return (
233 <View pop={pop} inset>
234 <NavHeader>
235 <Crumbs
236 routes={[{ text: name, action: goBack }, { text: 'Residents' }]}
237 />
238 </NavHeader>
239 <Grid>
240 <Grid.Item full as={H4} className="mt4">
241 Residents
242 </Grid.Item>
243 <Grid.Divider />
244 <Grid.Item
245 full
246 as={ForwardButton}
247 disabled={!canSpawn}
248 onClick={goIssuePoint}>
249 Spawn {isStar ? ' Planets' : ' Stars'}
250 </Grid.Item>
251 <Grid.Divider />
252 {senateButton}
253 <Grid.Item
254 full
255 className="mt1"
256 as={Tabs}
257 // Tabs
258 views={VIEWS}
259 options={OPTIONS}
260 currentTab={currentTab}
261 onTabChange={setCurrentTab}
262 // Props for tab
263 points={points}
264 onAccept={onAccept}
265 onDecline={onDecline}
266 page={page}
267 setPage={setPage}
268 />
269 </Grid>
270 </View>
271 );