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';
6import { delegatedSending } from 'azimuth-js';
8import { usePointCursor } from 'store/pointCursor';
9import { usePointCache } from 'store/pointCache';
10import { useWallet } from 'store/wallet';
12import View from 'components/View';
13import Tabs from 'components/Tabs';
14import BarGraph from 'components/BarGraph';
15import MaybeSigil from 'components/MaybeSigil';
16import { matchBlinky } from 'components/Blinky';
17import Chip from 'components/Chip';
18import Crumbs from 'components/Crumbs';
20import { useLocalRouter } from 'lib/LocalRouter';
21import useInvites from 'lib/useInvites';
22import * as need from 'lib/need';
23import * as wg from 'lib/walletgen';
25import Inviter from 'views/Invite/Inviter';
27import { ReactComponent as SearchIcon } from 'assets/search.svg';
28import CopyButton from 'components/CopyButton';
29import { useSyncForeignPoints } from 'lib/useSyncPoints';
30import { useNetwork } from 'store/network';
31import NavHeader from 'components/NavHeader';
33function SearchInput({ className, value, onChange }) {
35 <Flex align="center" full className={cn('b b-gray2 b1', className)}>
36 <Flex.Item className="p2">
51function CohortMember({ point, pending = false, className, ...rest }) {
52 const { sentInvites } = useInvites(point);
53 const patp = useMemo(() => ob.patp(point), [point]);
54 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
56 const DetailText = useCallback(
58 pending ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
59 [pending, sentInvites]
63 <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
64 <Flex.Item className="w9 h9">
65 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
73 className="mono f6 ph4">
74 <Flex.Item>{patp}</Flex.Item>
75 <Flex.Item className="gray4">
89 const { syncInvites } = usePointCache();
91 const [query, setQuery] = useState('');
94 acceptedPoints.map(syncInvites);
95 }, [acceptedPoints, syncInvites]);
97 const handleChange = useCallback(
99 setQuery(e.target.value);
105 const filterPoints = useCallback(
108 const patp = ob.patp(p).slice(1);
109 return patp.startsWith(query);
113 const selectInvite = useCallback(
115 onSelectInvite(point);
119 const _pendingPoints = filterPoints(pendingPoints);
120 const _acceptedPoints = filterPoints(acceptedPoints);
122 const offset = onlyPending ? 0 : _acceptedPoints.length;
125 <Grid gap={3} className={cn('mt4', className)}>
126 <Grid.Item full as={SearchInput} value={query} onChange={handleChange} />
129 _acceptedPoints.map((p, idx) => (
135 onClick={selectInvite(p)}
138 {_pendingPoints.map((p, idx) => (
141 half={((offset + idx) % 2) + 1}
145 onClick={selectInvite(p)}
150 {_pendingPoints.length === 0 &&
151 (_acceptedPoints.length === 0 || onlyPending) && (
152 <Grid.Item full className="p4 t-center">
154 No invites accepted yet.
161function hideIf(hider, hidden) {
162 return hider ? '●●●●●●●' : hidden;
165function CohortMemberExpanded({ point, className, ...rest }) {
166 const { sentInvites, acceptedInvites } = useInvites(point);
167 const { getDetails } = usePointCache();
168 const { authToken } = useWallet();
169 const { contracts } = useNetwork();
170 const details = getDetails(point);
171 const { active } = details.getOrElse({});
172 const patp = useMemo(() => ob.patp(point), [point]);
173 const colors = useMemo(
174 () => (!active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF']),
178 const [code, setCode] = useState(Nothing());
179 const [codeVisible, setCodeVisible] = useState(false);
182 const fetchWallet = async () => {
183 const _authToken = authToken.getOrElse(null);
184 const _details = details.getOrElse(null);
185 const _contracts = contracts.getOrElse(null);
196 const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
201 if (owner.keys.address !== _details.transferProxy) {
204 setCode(Just(ticket));
208 }, [authToken, codeVisible, contracts, details, point]);
211 setCodeVisible(false);
215 <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
216 <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
217 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
220 <Grid.Item cols={[4, 13]} className="mono mt-auto ">
228 className="mt-auto mb1 f6 gray4"
233 <Grid.Item cols={[4, 10]} className="mb-auto mt1 f6 gray4">
234 {(code.getOrElse(true) === null &&
235 'This invite code cannot be recovered') ||
236 hideIf(!codeVisible, matchBlinky(code))}
241 className="mh-auto f6 underline pointer"
242 onClick={() => setCodeVisible(true)}>
246 {codeVisible && code.getOrElse('') !== null && (
248 className="mh-auto f6 t-right"
251 text={code.getOrElse('')}
261 className="mt-auto mb1 f6 gray4"
264 {matchBlinky(sentInvites)} invites sent
268 className="mb-auto mt1 f6 gray4"
271 {matchBlinky(acceptedInvites)} invites accepted
279export default function InviteCohort() {
280 const { pop } = useLocalRouter();
282 const { pointCursor } = usePointCursor();
283 const point = need.point(pointCursor);
285 const { syncDetails } = usePointCache();
293 } = useInvites(point);
295 const [showInviteForm, setShowInviteForm] = useState(false);
296 const [tab, setTab] = useState(NAMES.ALL);
298 const _acceptedPoints = acceptedPoints.getOrElse([]);
299 const _pendingPoints = pendingPoints.getOrElse([]);
300 const _pendingInvites = _pendingPoints.length;
301 const _acceptedInvites = _acceptedPoints.length;
302 const _totalInvites = availableInvites
303 .map(a => sentInvites.getOrElse(0) + a)
306 const [selectedInvite, _setSelectedInvite] = useState();
308 useSyncForeignPoints([..._acceptedPoints, ..._pendingPoints]);
310 const setSelectedInvite = useCallback(
311 p => _setSelectedInvite(old => (old === p ? null : p)),
316 <View pop={pop} inset>
321 { text: ob.patp(point), action: () => pop() },
322 { text: 'Invite Group' },
327 <Grid.Item full>Invite Group</Grid.Item>
329 <Flex align="center">
331 {_acceptedInvites} / {_totalInvites}
333 {_pendingInvites !== 0 && (
334 <Flex.Item as={Chip} className="bg-yellow4 white">
335 {_pendingInvites} pending
343 available={availableInvites}
345 accepted={acceptedInvites}
347 {availableInvites.getOrElse(0) !== 0 && !showInviteForm && (
353 onClick={() => setShowInviteForm(true)}>
357 {showInviteForm && <Inviter />}
368 acceptedPoints={_acceptedPoints}
369 pendingPoints={_pendingPoints}
370 onlyPending={tab === NAMES.PENDING}
371 onSelectInvite={setSelectedInvite}
378 as={CohortMemberExpanded}
380 point={selectedInvite}
381 onBack={() => setSelectedInvite(null)}
394 [NAMES.PENDING]: CohortList,
395 [NAMES.ALL]: CohortList,
399 { text: 'All', value: NAMES.ALL },
400 { 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