import React, { useCallback, useEffect, useMemo } from 'react'; import { Just, Nothing } from 'folktale/maybe'; import { Grid, H5, H1, HelpText, LinkButton, Flex } from 'indigo-react'; import { get } from 'lodash'; import { useHistory } from 'store/history'; import { useWallet } from 'store/wallet'; import { usePointCache } from 'store/pointCache'; import { usePointCursor } from 'store/pointCursor'; import * as need from 'lib/need'; import { isZeroAddress, abbreviateAddress } from 'lib/wallet'; import useIsEclipticOwner from 'lib/useIsEclipticOwner'; import { useSyncKnownPoints } from 'lib/useSyncPoints'; import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers'; import pluralize from 'lib/pluralize'; import View from 'components/View'; import Blinky from 'components/Blinky'; import Passport from 'components/Passport'; import Footer from 'components/Footer'; import { ForwardButton } from 'components/Buttons'; import CopiableAddress from 'components/CopiableAddress'; const maybeGetResult = (obj, key, defaultValue) => obj.matchWith({ Nothing: () => defaultValue, Just: p => p.value.matchWith({ Ok: r => get(r.value, key, defaultValue), Error: e => defaultValue, }), }); const hasTransferProxy = details => !isZeroAddress(details.transferProxy); const PointList = function({ points, className, actions, ...rest }) { const { setPointCursor } = usePointCursor(); const { push, names } = useHistory(); return ( {points.map((point, i) => ( { setPointCursor(Just(point)); push(names.POINT); }} {...rest} /> {actions && ( {actions(point, i)} )} ))} ); }; function ActionButtons({ actions = [] }) { return ( {actions.map(action => ( {action.text} ))} ); } export default function Points() { const { wallet } = useWallet(); const { pop, push, popAndPush, names } = useHistory(); const { setPointCursor } = usePointCursor(); const { controlledPoints, getDetails } = usePointCache(); const isEclipticOwner = useIsEclipticOwner(); const [ rejectedPoints, addRejectedPoint, ] = useRejectedIncomingPointTransfers(); const maybeOutgoingPoints = useMemo( () => controlledPoints.chain(points => points.matchWith({ Error: () => Nothing(), Ok: c => { const points = c.value.ownedPoints.map(point => getDetails(point).chain(details => Just({ point: point, has: hasTransferProxy(details) }) ) ); // if we have details for every point, // return the array of pending transfers. if (points.every(p => Just.hasInstance(p))) { const outgoing = points .filter(p => p.value.has) .map(p => p.value.point); return Just(outgoing); } else { return Nothing(); } }, }) ), [getDetails, controlledPoints] ); // if we can only interact with a single point, forget about the existence // of this page and jump to the point page. // if there are any pending transfers, incoming or outgoing, stay on this // page, because those can only be completed/cancelled here. useEffect(() => { if (Nothing.hasInstance(maybeOutgoingPoints)) { return; } controlledPoints.matchWith({ Nothing: () => null, Just: r => { r.value.matchWith({ Error: () => null, Ok: c => { let all = [ ...c.value.ownedPoints, ...c.value.votingPoints, ...c.value.managingPoints, ...c.value.spawningPoints, ]; const incoming = c.value.incomingPoints.filter( p => !rejectedPoints.includes(p) ); if ( all.length === 1 && incoming.length === 0 && maybeOutgoingPoints.value.length === 0 ) { setPointCursor(Just(all[0])); popAndPush(names.POINT); } }, }); }, }); }, [ controlledPoints, rejectedPoints, maybeOutgoingPoints, setPointCursor, popAndPush, names, ]); const address = need.addressFromWallet(wallet); const loading = Nothing.hasInstance(controlledPoints); const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []); const incomingPoints = maybeGetResult( controlledPoints, 'incomingPoints', [] ).filter(point => !rejectedPoints.includes(point)); const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []); const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []); const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []); const outgoingPoints = maybeOutgoingPoints.getOrElse([]); const allPoints = [ ...ownedPoints.filter(p => !outgoingPoints.includes(p)), ...managingPoints, ...votingPoints, ...spawningPoints, ]; const displayEmptyState = !loading && incomingPoints.length === 0 && allPoints.length === 0; // sync display details for known points useSyncKnownPoints([ ...ownedPoints, ...incomingPoints, ...managingPoints, ...votingPoints, ...spawningPoints, ]); const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [ names.CREATE_GALAXY, push, ]); const goViewPoint = useCallback(() => push(names.VIEW_POINT), [ names.VIEW_POINT, push, ]); return ( {abbreviateAddress(address)} {loading && ( Loading... )} {displayEmptyState && ( No points to display. This wallet is not the owner or proxy for any points. )} {incomingPoints.length > 0 && ( {pluralize( incomingPoints.length, 'Incoming Transfer', 'Incoming Transfers' )} ( { setPointCursor(Just(point)); push(names.ACCEPT_TRANSFER); }, }, { text: 'Reject', onClick: () => { addRejectedPoint(point); }, }, ]} /> )} inverted /> )} {outgoingPoints.length > 0 && ( {pluralize( outgoingPoints.length, 'Outgoing Transfer', 'Outgoing Transfers' )} ( { setPointCursor(Just(point)); // TODO: deep linking to fix this duplicate route push(names.CANCEL_TRANSFER); }, }, ]} /> )} inverted /> )} {allPoints.length > 0 && ( {pluralize(allPoints.length, 'Point')} )} ); }