@@ -1,11 +1,12 @@
11import React, { useMemo, useEffect, useCallback, useState } from 'react';
22import { Grid, Flex, Button } from 'indigo-react';
33import * as ob from 'urbit-ob';
4−import { Just } from 'folktale/maybe';
4+import { Just, Nothing } from 'folktale/maybe';
55import cn from 'classnames';
66
77import { usePointCursor } from 'store/pointCursor';
88import { usePointCache } from 'store/pointCache';
9+import { useWallet } from 'store/wallet';
910
1011import View from 'components/View';
1112import Tabs from 'components/Tabs';
@@ -19,10 +20,13 @@ import Crumbs from 'components/Crumbs';
1920import { useLocalRouter } from 'lib/LocalRouter';
2021import useInvites from 'lib/useInvites';
2122import * as need from 'lib/need';
23+import * as wg from 'lib/walletgen';
2224
2325import Inviter from 'views/Invite/Inviter';
2426
2527import { ReactComponent as SearchIcon } from 'assets/search.svg';
28+import CopyButton from 'components/CopyButton';
29+import { useSyncForeignPoints } from 'lib/useSyncPoints';
2630
2731function SearchInput({ className, value, onChange }) {
2832 return (
@@ -42,7 +46,7 @@ function SearchInput({ className, value, onChange }) {
4246 );
4347}
4448
45−function CohortMember({ point, pending = false, className }) {
49+function CohortMember({ point, pending = false, className, ...rest }) {
4650 const { sentInvites } = useInvites(point);
4751 const patp = useMemo(() => ob.patp(point), [point]);
4852 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
@@ -54,7 +58,7 @@ function CohortMember({ point, pending = false, className }) {
5458 );
5559
5660 return (
57− <Flex justify="between" className={cn('b1 b-gray2', className)}>
61+ <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
5862 <Flex.Item className="w9 h9">
5963 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
6064 </Flex.Item>
@@ -73,7 +77,13 @@ function CohortMember({ point, pending = false, className }) {
7377 </Flex>
7478 );
7579}
76−function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
80+function CohortList({
81+ acceptedPoints,
82+ pendingPoints,
83+ className,
84+ onlyPending,
85+ onSelectInvite,
86+}) {
7787 const { syncInvites } = usePointCache();
7888
7989 const [query, setQuery] = useState('');
@@ -98,7 +108,12 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
98108 }),
99109 [query]
100110 );
101−
111+ const selectInvite = useCallback(
112+ point => () => {
113+ onSelectInvite(point);
114+ },
115+ [onSelectInvite]
116+ );
102117 const _pendingPoints = filterPoints(pendingPoints);
103118 const _acceptedPoints = filterPoints(acceptedPoints);
104119
@@ -115,6 +130,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
115130 half={(idx % 2) + 1}
116131 as={CohortMember}
117132 point={p}
133+ onClick={selectInvite(p)}
118134 />
119135 ))}
120136 {_pendingPoints.map((p, idx) => (
@@ -124,6 +140,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
124140 as={CohortMember}
125141 point={p}
126142 pending
143+ onClick={selectInvite(p)}
127144 />
128145 ))}
129146 </>
@@ -139,12 +156,122 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
139156 );
140157}
141158
159+function hideIf(hider, hidden) {
160+ return hider ? '●●●●●●●' : hidden;
161+}
162+
163+function CohortMemberExpanded({ point, className, ...rest }) {
164+ const { sentInvites, acceptedInvites } = useInvites(point);
165+ const { getDetails } = usePointCache();
166+ const { authToken } = useWallet();
167+ const details = getDetails(point).getOrElse({});
168+ const { active } = details;
169+ const patp = useMemo(() => ob.patp(point), [point]);
170+ const colors = !active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
171+
172+ const [code, setCode] = useState(Nothing());
173+ const [codeVisible, setCodeVisible] = useState(false);
174+
175+ useEffect(() => {
176+ const fetchWallet = async () => {
177+ const _authToken = authToken.getOrElse(null);
178+ if (!_authToken || active) {
179+ return;
180+ }
181+ const ticket = await wg.makeDeterministicTicket(point, _authToken);
182+ setCode(Just(ticket));
183+ };
184+ fetchWallet();
185+ }, [authToken, point, active]);
186+
187+ const DetailText = useCallback(
188+ () =>
189+ !active ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
190+ [active, sentInvites]
191+ );
192+
193+ return (
194+ <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
195+ <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
196+ <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
197+ </Grid.Item>
198+
199+ <Grid.Item cols={[4, 13]} className="mono mt-auto ">
200+ {patp}
201+ </Grid.Item>
202+
203+ {!active && (
204+ <>
205+ <Grid.Item
206+ cols={[4, 13]}
207+ className="mt-auto mb1 f6 gray4"
208+ as={Flex}
209+ col>
210+ Invite Code
211+ </Grid.Item>
212+ <Grid.Item
213+ cols={[4, 10]}
214+ className="mb-auto mt1 f6 gray4"
215+ as={Flex}
216+ col
217+ onClick={() => setCodeVisible(r => !r)}>
218+ {hideIf(!codeVisible, matchBlinky(code))}
219+ </Grid.Item>
220+ <Grid.Item
221+ className="mh-auto f6"
222+ cols={[10, 13]}
223+ as={CopyButton}
224+ text={code.getOrElse('')}
225+ />
226+ </>
227+ )}
228+
229+ {active && (
230+ <>
231+ <Grid.Item
232+ cols={[4, 13]}
233+ className="mt-auto mb1 f6 gray4"
234+ as={Flex}
235+ col>
236+ {matchBlinky(sentInvites)} invites sent
237+ </Grid.Item>
238+ <Grid.Item
239+ cols={[4, 13]}
240+ className="mb-auto mt1 f6 gray4"
241+ as={Flex}
242+ col>
243+ {matchBlinky(acceptedInvites)} invites accepted
244+ </Grid.Item>
245+ </>
246+ )}
247+
248+ {/* <Flex.Item>Invite Code</Flex.Item> */}
249+ {/* <Flex.Item>{matchBlinky(code)}</Flex.Item> */}
250+ {/* <span className="pointer underline">Get Invite code </span> */}
251+ {/* <br /> */}
252+ {/* {resend && <span>{matchBlinky(code)}</span>} */}
253+ {/* </Grid.Item> */}
254+ {/* {resend && ( */}
255+ {/* <Grid.Item cols={[5, 13]} rows={[2, 3]} className="mv-auto"></Grid.Item> */}
256+ {/* )} */}
257+ {/* <Grid.Item justify="evenly" as={Flex} col className="mono f6 ph4"> */}
258+ {/* <Flex.Item>{patp}</Flex.Item> */}
259+ {/* <Flex.Item className="gray4"> */}
260+ {/* <DetailText /> */}
261+ {/* </Flex.Item> */}
262+ {/* </Grid.Item> */}
263+ </Grid>
264+ );
265+}
266+
142267export default function InviteCohort() {
143268 const { pop } = useLocalRouter();
144269
145270 const { pointCursor } = usePointCursor();
146271 const point = need.point(pointCursor);
147272
273+ const { syncDetails } = usePointCache();
274+
148275 const {
149276 acceptedInvites,
150277 acceptedPoints,
@@ -164,6 +291,17 @@ export default function InviteCohort() {
164291 .map(a => sentInvites.getOrElse(0) + a)
165292 .getOrElse(0);
166293
294+ const [selectedInvite, _setSelectedInvite] = useState();
295+
296+ useEffect(() => {
297+ Promise.all(_acceptedPoints.map(i => syncDetails(i)));
298+ }, [_acceptedPoints, syncDetails]);
299+
300+ const setSelectedInvite = useCallback(
301+ p => _setSelectedInvite(old => (old === p ? null : p)),
302+ []
303+ );
304+
167305 return (
168306 <View pop={pop} inset>
169307 <Grid gap={3}>
@@ -209,18 +347,31 @@ export default function InviteCohort() {
209347 )}
210348 {showInviteForm && <Inviter />}
211349
212− <Grid.Item
213− full
214− center
215− as={Tabs}
216− views={VIEWS}
217− options={OPTIONS}
218− currentTab={tab}
219− onTabChange={setTab}
220− acceptedPoints={_acceptedPoints}
221− pendingPoints={_pendingPoints}
222− onlyPending={tab === NAMES.PENDING}
223− />
350+ {true && (
351+ <Grid.Item
352+ full
353+ center
354+ as={Tabs}
355+ views={VIEWS}
356+ options={OPTIONS}
357+ currentTab={tab}
358+ onTabChange={setTab}
359+ acceptedPoints={_acceptedPoints}
360+ pendingPoints={_pendingPoints}
361+ onlyPending={tab === NAMES.PENDING}
362+ onSelectInvite={setSelectedInvite}
363+ />
364+ )}
365+ {selectedInvite && (
366+ <Grid.Item
367+ full
368+ center
369+ as={CohortMemberExpanded}
370+ pending
371+ point={selectedInvite}
372+ onBack={() => setSelectedInvite(null)}
373+ />
374+ )}
224375 </Grid>
225376 </View>
226377 );