import React, { useMemo, useEffect, useCallback, useState } from 'react';
import { Grid, Flex, Button } from 'indigo-react';
import * as ob from 'urbit-ob';
import { Just, Nothing } from 'folktale/maybe';
import cn from 'classnames';
import { delegatedSending } from 'azimuth-js';
import { usePointCursor } from 'store/pointCursor';
import { usePointCache } from 'store/pointCache';
import { useWallet } from 'store/wallet';
import View from 'components/View';
import Tabs from 'components/Tabs';
import BarGraph from 'components/BarGraph';
import MaybeSigil from 'components/MaybeSigil';
import { matchBlinky } from 'components/Blinky';
import Chip from 'components/Chip';
import Crumbs from 'components/Crumbs';
import { useLocalRouter } from 'lib/LocalRouter';
import useInvites from 'lib/useInvites';
import * as need from 'lib/need';
import * as wg from 'lib/walletgen';
import Inviter from 'views/Invite/Inviter';
import { ReactComponent as SearchIcon } from 'assets/search.svg';
import CopyButton from 'components/CopyButton';
import { useSyncForeignPoints } from 'lib/useSyncPoints';
import { useNetwork } from 'store/network';
import NavHeader from 'components/NavHeader';
function SearchInput({ className, value, onChange }) {
return (
);
}
function CohortMember({ point, pending = false, className, ...rest }) {
const { sentInvites } = useInvites(point);
const patp = useMemo(() => ob.patp(point), [point]);
const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
const DetailText = useCallback(
() =>
pending ? 'Pending' : <> {matchBlinky(sentInvites)} points invited >,
[pending, sentInvites]
);
return (
{patp}
);
}
function CohortList({
acceptedPoints,
pendingPoints,
className,
onlyPending,
onSelectInvite,
}) {
const { syncInvites } = usePointCache();
const [query, setQuery] = useState('');
useEffect(() => {
acceptedPoints.map(syncInvites);
}, [acceptedPoints, syncInvites]);
const handleChange = useCallback(
e => {
setQuery(e.target.value);
e.preventDefault();
},
[setQuery]
);
const filterPoints = useCallback(
points =>
points.filter(p => {
const patp = ob.patp(p).slice(1);
return patp.startsWith(query);
}),
[query]
);
const selectInvite = useCallback(
point => () => {
onSelectInvite(point);
},
[onSelectInvite]
);
const _pendingPoints = filterPoints(pendingPoints);
const _acceptedPoints = filterPoints(acceptedPoints);
const offset = onlyPending ? 0 : _acceptedPoints.length;
return (
<>
{!onlyPending &&
_acceptedPoints.map((p, idx) => (
))}
{_pendingPoints.map((p, idx) => (
))}
>
{_pendingPoints.length === 0 &&
(_acceptedPoints.length === 0 || onlyPending) && (
{' '}
No invites accepted yet.
)}
);
}
function hideIf(hider, hidden) {
return hider ? '●●●●●●●' : hidden;
}
function CohortMemberExpanded({ point, className, ...rest }) {
const { sentInvites, acceptedInvites } = useInvites(point);
const { getDetails } = usePointCache();
const { authToken } = useWallet();
const { contracts } = useNetwork();
const details = getDetails(point);
const { active } = details.getOrElse({});
const patp = useMemo(() => ob.patp(point), [point]);
const colors = useMemo(
() => (!active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF']),
[active]
);
const [code, setCode] = useState(Nothing());
const [codeVisible, setCodeVisible] = useState(false);
useEffect(() => {
const fetchWallet = async () => {
const _authToken = authToken.getOrElse(null);
const _details = details.getOrElse(null);
const _contracts = contracts.getOrElse(null);
if (
!_authToken ||
!_details ||
_details.active ||
!_contracts ||
!codeVisible
) {
setCode(Nothing());
return;
}
const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
point,
_authToken
);
if (owner.keys.address !== _details.transferProxy) {
setCode(Just(null));
} else {
setCode(Just(ticket));
}
};
fetchWallet();
}, [authToken, codeVisible, contracts, details, point]);
useEffect(() => {
setCodeVisible(false);
}, [point]);
return (
{patp}
{!active && (
<>
Invite Code
{(code.getOrElse(true) === null &&
'This invite code cannot be recovered') ||
hideIf(!codeVisible, matchBlinky(code))}
{!codeVisible && (
setCodeVisible(true)}>
Show
)}
{codeVisible && code.getOrElse('') !== null && (
)}
>
)}
{active && (
<>
{matchBlinky(sentInvites)} invites sent
{matchBlinky(acceptedInvites)} invites accepted
>
)}
);
}
export default function InviteCohort() {
const { pop } = useLocalRouter();
const { pointCursor } = usePointCursor();
const point = need.point(pointCursor);
const { syncDetails } = usePointCache();
const {
acceptedInvites,
acceptedPoints,
pendingPoints,
availableInvites,
sentInvites,
} = useInvites(point);
const [showInviteForm, setShowInviteForm] = useState(false);
const [tab, setTab] = useState(NAMES.ALL);
const _acceptedPoints = acceptedPoints.getOrElse([]);
const _pendingPoints = pendingPoints.getOrElse([]);
const _pendingInvites = _pendingPoints.length;
const _acceptedInvites = _acceptedPoints.length;
const _totalInvites = availableInvites
.map(a => sentInvites.getOrElse(0) + a)
.getOrElse(0);
const [selectedInvite, _setSelectedInvite] = useState();
useSyncForeignPoints([..._acceptedPoints, ..._pendingPoints]);
const setSelectedInvite = useCallback(
p => _setSelectedInvite(old => (old === p ? null : p)),
[]
);
return (
pop() },
{ text: 'Invite Group' },
]}
/>
Invite Group
{_acceptedInvites} / {_totalInvites}
{_pendingInvites !== 0 && (
{_pendingInvites} pending
)}
{availableInvites.getOrElse(0) !== 0 && !showInviteForm && (
setShowInviteForm(true)}>
Add Members
)}
{showInviteForm && }
{true && (
)}
{selectedInvite && (
setSelectedInvite(null)}
/>
)}
);
}
const NAMES = {
ALL: 'ALL',
PENDING: 'PENDING',
};
const VIEWS = {
[NAMES.PENDING]: CohortList,
[NAMES.ALL]: CohortList,
};
const OPTIONS = [
{ text: 'All', value: NAMES.ALL },
{ text: 'Pending', value: NAMES.PENDING },
];