1import React, { useMemo, useEffect, useCallback, useState } from 'react';
2import { Grid, Flex, Button } from 'indigo-react';
3import * as ob from 'urbit-ob';
4import { Just, Nothing } from 'folktale/maybe';
5import cn from 'classnames';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
9import { useWallet } from 'store/wallet';
11import View from 'components/View';
12import Tabs from 'components/Tabs';
13import BarGraph from 'components/BarGraph';
14import MaybeSigil from 'components/MaybeSigil';
15import { matchBlinky } from 'components/Blinky';
16import Chip from 'components/Chip';
17import Crumbs from 'components/Crumbs';
19import { useLocalRouter } from 'lib/LocalRouter';
20import useInvites from 'lib/useInvites';
21import * as need from 'lib/need';
22import * as wg from 'lib/walletgen';
24import Inviter from 'views/Invite/Inviter';
26import { ReactComponent as SearchIcon } from 'assets/search.svg';
27import CopyButton from 'components/CopyButton';
28import { useSyncDetails } from 'lib/useSyncPoints';
29import { useNetwork } from 'store/network';
30import NavHeader from 'components/NavHeader';
32function SearchInput({ className, value, onChange }) {
34 <Flex align="center" full className={cn('b b-gray2 b1', className)}>
35 <Flex.Item className="p2">
50function CohortMember({ point, pending = false, className, ...rest }) {
51 const { sentInvites } = useInvites(point);
52 const patp = useMemo(() => ob.patp(point), [point]);
53 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
55 const DetailText = useCallback(
57 pending ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
58 [pending, sentInvites]
62 <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
63 <Flex.Item className="w9 h9">
64 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
72 className="mono f6 ph4">
73 <Flex.Item>{patp}</Flex.Item>
74 <Flex.Item className="gray4">
88 const { syncInvites } = usePointCache();
90 const [query, setQuery] = useState('');
93 acceptedPoints.map(syncInvites);
94 }, [acceptedPoints, syncInvites]);
96 const handleChange = useCallback(
98 setQuery(e.target.value);
104 const filterPoints = useCallback(
107 const patp = ob.patp(p).slice(1);
108 return patp.startsWith(query);
112 const selectInvite = useCallback(
114 onSelectInvite(point);
118 const _pendingPoints = filterPoints(pendingPoints);
119 const _acceptedPoints = filterPoints(acceptedPoints);
121 const offset = onlyPending ? 0 : _acceptedPoints.length;
124 <Grid gap={3} className={cn('mt4', className)}>
125 <Grid.Item full as={SearchInput} value={query} onChange={handleChange} />
128 _acceptedPoints.map((p, idx) => (
134 onClick={selectInvite(p)}
137 {_pendingPoints.map((p, idx) => (
140 half={((offset + idx) % 2) + 1}
144 onClick={selectInvite(p)}
149 {_pendingPoints.length === 0 &&
150 (_acceptedPoints.length === 0 || onlyPending) && (
151 <Grid.Item full className="p4 t-center">
153 No invites accepted yet.
160function hideIf(hider, hidden) {
161 return hider ? '●●●●●●●' : hidden;
164function CohortMemberExpanded({ point, className, ...rest }) {
165 const { sentInvites, acceptedInvites } = useInvites(point);
166 const { getDetails } = usePointCache();
167 const { authToken } = useWallet();
168 const { contracts } = useNetwork();
169 const details = getDetails(point);
170 const { active } = details.getOrElse({});
171 const patp = useMemo(() => ob.patp(point), [point]);
172 const colors = useMemo(
173 () => (!active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF']),
177 const [code, setCode] = useState(Nothing());
178 const [codeVisible, setCodeVisible] = useState(false);
181 const fetchWallet = async () => {
182 const _authToken = authToken.getOrElse(null);
183 const _details = details.getOrElse(null);
184 const _contracts = contracts.getOrElse(null);
195 const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
200 if (owner.keys.address !== _details.transferProxy) {
203 setCode(Just(ticket));
207 }, [authToken, codeVisible, contracts, details, point]);
210 setCodeVisible(false);
214 <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
215 <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
216 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
219 <Grid.Item cols={[4, 13]} className="mono mt-auto ">
227 className="mt-auto mb1 f6 gray4"
232 <Grid.Item cols={[4, 10]} className="mb-auto mt1 f6 gray4">
233 {(code.getOrElse(true) === null &&
234 'This invite code cannot be recovered') ||
235 hideIf(!codeVisible, matchBlinky(code))}
240 className="mh-auto f6 underline pointer"
241 onClick={() => setCodeVisible(true)}>
245 {codeVisible && code.getOrElse('') !== null && (
247 className="mh-auto f6 t-right"
250 text={code.getOrElse('')}
260 className="mt-auto mb1 f6 gray4"
263 {matchBlinky(sentInvites)} invites sent
267 className="mb-auto mt1 f6 gray4"
270 {matchBlinky(acceptedInvites)} invites accepted
278export default function InviteCohort() {
279 const { pop } = useLocalRouter();
281 const { pointCursor } = usePointCursor();
282 const point = need.point(pointCursor);
290 } = useInvites(point);
292 const [showInviteForm, setShowInviteForm] = useState(false);
293 const [tab, setTab] = useState(NAMES.ALL);
295 const _acceptedPoints = acceptedPoints.getOrElse([]);
296 const _pendingPoints = pendingPoints.getOrElse([]);
297 const _pendingInvites = _pendingPoints.length;
298 const _acceptedInvites = _acceptedPoints.length;
299 const _totalInvites = availableInvites
300 .map(a => sentInvites.getOrElse(0) + a)
303 const [selectedInvite, _setSelectedInvite] = useState();
305 useSyncDetails([..._acceptedPoints, ..._pendingPoints]);
307 const setSelectedInvite = useCallback(
308 p => _setSelectedInvite(old => (old === p ? null : p)),
313 <View pop={pop} inset>
318 { text: ob.patp(point), action: () => pop() },
319 { text: 'Invite Group' },
324 <Grid.Item full>Invite Group</Grid.Item>
326 <Flex align="center">
328 {_acceptedInvites} / {_totalInvites}
330 {_pendingInvites !== 0 && (
331 <Flex.Item as={Chip} className="bg-yellow4 white">
332 {_pendingInvites} pending
340 available={availableInvites}
342 accepted={acceptedInvites}
344 {availableInvites.getOrElse(0) !== 0 && !showInviteForm && (
350 onClick={() => setShowInviteForm(true)}>
354 {showInviteForm && <Inviter />}
365 acceptedPoints={_acceptedPoints}
366 pendingPoints={_pendingPoints}
367 onlyPending={tab === NAMES.PENDING}
368 onSelectInvite={setSelectedInvite}
375 as={CohortMemberExpanded}
377 point={selectedInvite}
378 onBack={() => setSelectedInvite(null)}
391 [NAMES.PENDING]: CohortList,
392 [NAMES.ALL]: CohortList,
396 { text: 'All', value: NAMES.ALL },
397 { text: 'Pending', value: NAMES.PENDING },
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list