import React, { useCallback, useEffect, useMemo } from 'react';
import { Just, Nothing } from 'folktale/maybe';
import { Grid, H5, HelpText, LinkButton, Flex } from 'indigo-react';
import { useHistory } from 'store/history';
import { useWallet } from 'store/wallet';
import { usePointCache } from 'store/pointCache';
import { usePointCursor } from 'store/pointCursor';
import { useStarReleaseCache } from 'store/starRelease';
import { useNetwork } from 'store/network';
import { useRollerStore } from 'store/rollerStore';
import * as need from 'lib/need';
import { abbreviateAddress } from 'lib/utils/address';
import useIsEclipticOwner from 'lib/useIsEclipticOwner';
import { useSyncDetails } from 'lib/useSyncPoints';
import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
import { pluralize } from 'lib/pluralize';
import Point from 'lib/types/Point';
import newGithubIssueUrl from 'new-github-issue-url';
import View from 'components/View';
import Footer from 'components/Footer';
import { ForwardButton } from 'components/Buttons';
import CopiableAddress from 'components/copiable/CopiableAddress';
import NavHeader from 'components/NavHeader';
import L2PointHeader from 'components/L2/Headers/L2PointHeader';
import IncomingPoint from 'components/L2/Points/IncomingPoint';
import LoadingOverlay from 'components/L2/LoadingOverlay';
import PointList from 'components/L2/PointList';
import { Box, Icon, Text } from '@tlon/indigo-react';
import './Points.scss';
import { StarReleaseButton } from './Points/StarReleaseButton';
import { maybeGetResult } from 'lib/maybeGetResult';
import { isGalaxy, isStar } from 'lib/utils/point';
import { L2VulnAlert } from 'components/alerts/L2VulnAlert';
export const isLocked = (details: any, contracts: any) =>
details.owner === contracts.linearSR ||
details.owner === contracts.conditionalSR;
interface ActionButtonProps {
actions?: {
text?: string;
onClick?: () => void;
}[];
}
function ActionButtons({ actions = [] }: ActionButtonProps) {
return (
{actions.map(action => (
{action.text}
))}
);
}
export default function Points() {
const { wallet }: any = useWallet();
const { pop, push, names }: any = useHistory();
const { setPointCursor }: any = usePointCursor();
const { controlledPoints, getDetails }: any = usePointCache();
const { contracts }: any = useNetwork();
const isEclipticOwner = useIsEclipticOwner();
const [
rejectedPoints,
addRejectedPoint,
] = useRejectedIncomingPointTransfers();
const { pointList } = useRollerStore();
const {
syncStarReleaseDetails,
starReleaseDetails,
}: any = useStarReleaseCache();
const outgoingPoints = useMemo(
() => pointList.filter(({ isOutgoing }) => isOutgoing),
[pointList]
);
const incomingPoints = useMemo(
() =>
pointList.filter(
({ isTransferProxy, value }) =>
isTransferProxy && !rejectedPoints.includes(value)
),
[pointList, rejectedPoints]
);
const _contracts = need.contracts(contracts);
const maybeLockedPoints = useMemo(
() =>
controlledPoints.chain((points: any) =>
points.matchWith({
Error: () => Nothing(),
Ok: (c: any) => {
const points = c.value.ownedPoints.map((point: number) =>
getDetails(point).chain((details: any) =>
Just({ point, has: isLocked(details, _contracts) })
)
);
// if we have details for every point,
// return the array of pending transfers.
if (points.every((p: any) => Just.hasInstance(p))) {
const locked = points
.filter((p: any) => p.value.has)
.map((p: any) => p.value.point);
return Just(locked);
} else {
return Nothing();
}
},
})
),
[getDetails, controlledPoints, _contracts]
);
const allPoints = useMemo(
() =>
pointList.filter(
({ isTransferProxy, isTransferProxySet, shouldDisplay, value }) =>
shouldDisplay &&
!(isTransferProxy || isTransferProxySet) &&
!rejectedPoints.includes(value)
),
[pointList, rejectedPoints]
);
// if we can only interact with a single point, 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(starReleaseDetails)) {
return;
}
if (
allPoints.length === 1 &&
incomingPoints.length === 0 &&
outgoingPoints.length === 0 &&
(starReleaseDetails.value === null ||
starReleaseDetails.value.total === 0)
) {
setPointCursor(Just(allPoints[0].value));
push(names.POINT);
}
}, [
allPoints,
rejectedPoints,
outgoingPoints,
incomingPoints,
setPointCursor,
push,
names,
starReleaseDetails,
]);
const address = need.addressFromWallet(wallet);
const loading = Nothing.hasInstance(controlledPoints);
const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
const lockedPoints = maybeLockedPoints
.getOrElse([])
.map((value: number) => ({ value }));
const displayEmptyState =
!loading && incomingPoints.length === 0 && allPoints.length === 0;
// If the user has any stars or galaxies, give a polite heads up that
// it can take some time for the roller be aware of L1 TXs
const showMigrateNotice =
!displayEmptyState &&
allPoints.some(p => isStar(p.value) || isGalaxy(p.value));
const starReleasing = starReleaseDetails
.map((s: any) => (s ? s.total > 0 : false))
.getOrElse(false);
useEffect(() => {
syncStarReleaseDetails();
}, [syncStarReleaseDetails]);
useEffect(() => {
if (
'Notification' in window &&
Notification.permission !== 'denied' &&
Notification.permission !== 'granted'
) {
Notification.requestPermission();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// sync display details for known points
useSyncDetails(ownedPoints);
const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
names.CREATE_GALAXY,
push,
]);
const goStarRelease = useCallback(() => push(names.STAR_RELEASE), [
names.STAR_RELEASE,
push,
]);
// if we got an error result, we should display it, instead of showing
// a potentially inaccurately empty point list.
//
if (
Just.hasInstance(controlledPoints) &&
controlledPoints.value.matchWith({
Error: (e: any) => true,
Ok: (v: any) => false,
})
) {
const url = newGithubIssueUrl({
user: 'urbit',
repo: 'bridge',
title: controlledPoints.value.value,
body: ``,
});
return (
{controlledPoints.value.value}
Try reloading the page. If this problem persists, please{' '}
file an issue on GitHub.
);
}
if (
loading ||
(allPoints.length === 1 &&
incomingPoints.length === 0 &&
outgoingPoints.length === 0 &&
!starReleasing)
) {
return ;
}
return (
}>
{abbreviateAddress(address)}
{incomingPoints.map(({ value }) => (
{
setPointCursor(Just(value));
push(names.ACCEPT_TRANSFER);
}}
reject={() => addRejectedPoint(value)}
/>
))}
{displayEmptyState && (
No Urbit IDs here yet
)}
{outgoingPoints.length > 0 && (
{pluralize(
outgoingPoints.length,
'Outgoing Transfer',
'Outgoing Transfers'
)}
(
{
setPointCursor(Just(point.value));
// TODO: deep linking to fix this duplicate route
push(names.CANCEL_TRANSFER);
},
},
]}
/>
)}
/>
)}
{allPoints.length > 0 && (
)}
{lockedPoints.length > 0 && (
)}
{showMigrateNotice && (
Not seeing a recently-migrated Layer 2 point? It may take a few
minutes for the public roller to be aware of the transaction.
)}
);
}