@@ -1,11 +1,13 @@
1import React, { useMemo, useEffect, useCallback, useState } from 'react';
1import React, { useMemo, useEffect, useCallback, useState } from 'react';
2import { Grid, Flex, Button } from 'indigo-react';
2import { Grid, Flex, Button } from 'indigo-react';
3import * as ob from 'urbit-ob';
3import * as ob from 'urbit-ob';
4−import { Just } from 'folktale/maybe';
4+import { Just, Nothing } from 'folktale/maybe';
5import cn from 'classnames';
5import cn from 'classnames';
6+import { delegatedSending } from 'azimuth-js';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
9import { usePointCache } from 'store/pointCache';
10+import { useWallet } from 'store/wallet';
10import View from 'components/View';
12import View from 'components/View';
11import Tabs from 'components/Tabs';
13import Tabs from 'components/Tabs';
@@ -19,10 +21,14 @@ import Crumbs from 'components/Crumbs';
19import { useLocalRouter } from 'lib/LocalRouter';
21import { useLocalRouter } from 'lib/LocalRouter';
20import useInvites from 'lib/useInvites';
22import useInvites from 'lib/useInvites';
21import * as need from 'lib/need';
23import * as need from 'lib/need';
24+import * as wg from 'lib/walletgen';
23import Inviter from 'views/Invite/Inviter';
26import Inviter from 'views/Invite/Inviter';
25import { ReactComponent as SearchIcon } from 'assets/search.svg';
28import { ReactComponent as SearchIcon } from 'assets/search.svg';
29+import CopyButton from 'components/CopyButton';
30+import { useSyncForeignPoints } from 'lib/useSyncPoints';
31+import { useNetwork } from 'store/network';
27function SearchInput({ className, value, onChange }) {
33function SearchInput({ className, value, onChange }) {
@@ -42,7 +48,7 @@ function SearchInput({ className, value, onChange }) {
45−function CohortMember({ point, pending = false, className }) {
51+function CohortMember({ point, pending = false, className, ...rest }) {
46 const { sentInvites } = useInvites(point);
52 const { sentInvites } = useInvites(point);
47 const patp = useMemo(() => ob.patp(point), [point]);
53 const patp = useMemo(() => ob.patp(point), [point]);
48 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
54 const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
@@ -54,7 +60,7 @@ function CohortMember({ point, pending = false, className }) {
57− <Flex justify="between" className={cn('b1 b-gray2', className)}>
63+ <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
58 <Flex.Item className="w9 h9">
64 <Flex.Item className="w9 h9">
59 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
65 <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
60 </Flex.Item>
66 </Flex.Item>
@@ -73,7 +79,13 @@ function CohortMember({ point, pending = false, className }) {
76−function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
82+function CohortList({
77 const { syncInvites } = usePointCache();
89 const { syncInvites } = usePointCache();
79 const [query, setQuery] = useState('');
91 const [query, setQuery] = useState('');
@@ -98,7 +110,12 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
101−
113+ const selectInvite = useCallback(
115+ onSelectInvite(point);
102 const _pendingPoints = filterPoints(pendingPoints);
119 const _pendingPoints = filterPoints(pendingPoints);
103 const _acceptedPoints = filterPoints(acceptedPoints);
120 const _acceptedPoints = filterPoints(acceptedPoints);
@@ -115,6 +132,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
115 half={(idx % 2) + 1}
132 half={(idx % 2) + 1}
116 as={CohortMember}
133 as={CohortMember}
117 point={p}
134 point={p}
135+ onClick={selectInvite(p)}
120 {_pendingPoints.map((p, idx) => (
138 {_pendingPoints.map((p, idx) => (
@@ -124,6 +142,7 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
124 as={CohortMember}
142 as={CohortMember}
125 point={p}
143 point={p}
145+ onClick={selectInvite(p)}
@@ -139,12 +158,132 @@ function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
161+function hideIf(hider, hidden) {
162+ return hider ? '●●●●●●●' : hidden;
165+function 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) {
202+ setCode(Just(null));
204+ setCode(Just(ticket));
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
142export default function InviteCohort() {
279export default function InviteCohort() {
143 const { pop } = useLocalRouter();
280 const { pop } = useLocalRouter();
145 const { pointCursor } = usePointCursor();
282 const { pointCursor } = usePointCursor();
146 const point = need.point(pointCursor);
283 const point = need.point(pointCursor);
285+ const { syncDetails } = usePointCache();
149 acceptedInvites,
288 acceptedInvites,
150 acceptedPoints,
289 acceptedPoints,
@@ -164,6 +303,15 @@ export default function InviteCohort() {
164 .map(a => sentInvites.getOrElse(0) + a)
303 .map(a => sentInvites.getOrElse(0) + a)
165 .getOrElse(0);
304 .getOrElse(0);
306+ const [selectedInvite, _setSelectedInvite] = useState();
308+ useSyncForeignPoints([..._acceptedPoints, ..._pendingPoints]);
310+ const setSelectedInvite = useCallback(
311+ p => _setSelectedInvite(old => (old === p ? null : p)),
168 <View pop={pop} inset>
316 <View pop={pop} inset>
169 <Grid gap={3}>
317 <Grid gap={3}>
@@ -209,18 +357,31 @@ export default function InviteCohort() {
210 {showInviteForm && <Inviter />}
358 {showInviteForm && <Inviter />}
212− <Grid.Item
361+ <Grid.Item
215− as={Tabs}
364+ as={Tabs}
216− views={VIEWS}
365+ views={VIEWS}
217− options={OPTIONS}
366+ options={OPTIONS}
218− currentTab={tab}
367+ currentTab={tab}
219− onTabChange={setTab}
368+ onTabChange={setTab}
220− acceptedPoints={_acceptedPoints}
369+ acceptedPoints={_acceptedPoints}
221− pendingPoints={_pendingPoints}
370+ pendingPoints={_pendingPoints}
222− onlyPending={tab === NAMES.PENDING}
371+ onlyPending={tab === NAMES.PENDING}
372+ onSelectInvite={setSelectedInvite}
375+ {selectedInvite && (
379+ as={CohortMemberExpanded}
381+ point={selectedInvite}
382+ onBack={() => setSelectedInvite(null)}