1import React, { useCallback, useEffect, useMemo } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, H5, HelpText, LinkButton, Flex } from 'indigo-react';
5import { useHistory } from 'store/history';
6import { useWallet } from 'store/wallet';
7import { usePointCache } from 'store/pointCache';
8import { usePointCursor } from 'store/pointCursor';
9import { useStarReleaseCache } from 'store/starRelease';
10import { useNetwork } from 'store/network';
11import { useRollerStore } from 'store/rollerStore';
13import * as need from 'lib/need';
14import { abbreviateAddress } from 'lib/utils/address';
15import useIsEclipticOwner from 'lib/useIsEclipticOwner';
16import { useSyncDetails } from 'lib/useSyncPoints';
17import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
18import { pluralize } from 'lib/pluralize';
19import Point from 'lib/types/Point';
20import newGithubIssueUrl from 'new-github-issue-url';
22import View from 'components/View';
23import Footer from 'components/Footer';
24import { ForwardButton } from 'components/Buttons';
25import CopiableAddress from 'components/copiable/CopiableAddress';
26import NavHeader from 'components/NavHeader';
27import L2PointHeader from 'components/L2/Headers/L2PointHeader';
28import IncomingPoint from 'components/L2/Points/IncomingPoint';
29import LoadingOverlay from 'components/L2/LoadingOverlay';
30import PointList from 'components/L2/PointList';
31import { Box, Icon, Text } from '@tlon/indigo-react';
33import './Points.scss';
34import { StarReleaseButton } from './Points/StarReleaseButton';
35import { maybeGetResult } from 'lib/maybeGetResult';
36import { isGalaxy, isStar } from 'lib/utils/point';
37import { L2VulnAlert } from 'components/alerts/L2VulnAlert';
39export const isLocked = (details: any, contracts: any) =>
40 details.owner === contracts.linearSR ||
41 details.owner === contracts.conditionalSR;
43interface ActionButtonProps {
50function ActionButtons({ actions = [] }: ActionButtonProps) {
53 {actions.map(action => (
58 onClick={action.onClick}>
66export default function Points() {
67 const { wallet }: any = useWallet();
68 const { pop, push, names }: any = useHistory();
69 const { setPointCursor }: any = usePointCursor();
70 const { controlledPoints, getDetails }: any = usePointCache();
71 const { contracts }: any = useNetwork();
72 const isEclipticOwner = useIsEclipticOwner();
76 ] = useRejectedIncomingPointTransfers();
77 const { pointList } = useRollerStore();
79 syncStarReleaseDetails,
81 }: any = useStarReleaseCache();
83 const outgoingPoints = useMemo(
84 () => pointList.filter(({ isOutgoing }) => isOutgoing),
88 const incomingPoints = useMemo(
91 ({ isTransferProxy, value }) =>
92 isTransferProxy && !rejectedPoints.includes(value)
94 [pointList, rejectedPoints]
97 const _contracts = need.contracts(contracts);
99 const maybeLockedPoints = useMemo(
101 controlledPoints.chain((points: any) =>
103 Error: () => Nothing(),
105 const points = c.value.ownedPoints.map((point: number) =>
106 getDetails(point).chain((details: any) =>
107 Just({ point, has: isLocked(details, _contracts) })
110 // if we have details for every point,
111 // return the array of pending transfers.
112 if (points.every((p: any) => Just.hasInstance(p))) {
113 const locked = points
114 .filter((p: any) => p.value.has)
115 .map((p: any) => p.value.point);
123 [getDetails, controlledPoints, _contracts]
126 const allPoints = useMemo(
129 ({ isTransferProxy, isTransferProxySet, shouldDisplay, value }) =>
131 !(isTransferProxy || isTransferProxySet) &&
132 !rejectedPoints.includes(value)
134 [pointList, rejectedPoints]
137 // if we can only interact with a single point, jump to the point page.
138 // if there are any pending transfers, incoming or outgoing, stay on this
139 // page, because those can only be completed/cancelled here.
141 if (Nothing.hasInstance(starReleaseDetails)) {
145 allPoints.length === 1 &&
146 incomingPoints.length === 0 &&
147 outgoingPoints.length === 0 &&
148 (starReleaseDetails.value === null ||
149 starReleaseDetails.value.total === 0)
151 setPointCursor(Just(allPoints[0].value));
165 const address = need.addressFromWallet(wallet);
167 const loading = Nothing.hasInstance(controlledPoints);
169 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
170 const lockedPoints = maybeLockedPoints
172 .map((value: number) => ({ value }));
174 const displayEmptyState =
175 !loading && incomingPoints.length === 0 && allPoints.length === 0;
177 // If the user has any stars or galaxies, give a polite heads up that
178 // it can take some time for the roller be aware of L1 TXs
179 const showMigrateNotice =
180 !displayEmptyState &&
181 allPoints.some(p => isStar(p.value) || isGalaxy(p.value));
183 const starReleasing = starReleaseDetails
184 .map((s: any) => (s ? s.total > 0 : false))
188 syncStarReleaseDetails();
189 }, [syncStarReleaseDetails]);
193 'Notification' in window &&
194 Notification.permission !== 'denied' &&
195 Notification.permission !== 'granted'
197 Notification.requestPermission();
199 }, []); // eslint-disable-line react-hooks/exhaustive-deps
201 // sync display details for known points
202 useSyncDetails(ownedPoints);
204 const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
209 const goStarRelease = useCallback(() => push(names.STAR_RELEASE), [
214 // if we got an error result, we should display it, instead of showing
215 // a potentially inaccurately empty point list.
218 Just.hasInstance(controlledPoints) &&
219 controlledPoints.value.matchWith({
220 Error: (e: any) => true,
221 Ok: (v: any) => false,
224 const url = newGithubIssueUrl({
227 title: controlledPoints.value.value,
228 body: `<!-- Please provide some context. Do you have Metamask installed? What login method did you use? -->`,
233 <Grid.Item full as={HelpText} className="mt8 t-center">
234 {controlledPoints.value.value}
236 Try reloading the page. If this problem persists, please{' '}
237 <a href={url}>file an issue on GitHub</a>.
246 (allPoints.length === 1 &&
247 incomingPoints.length === 0 &&
248 outgoingPoints.length === 0 &&
251 return <LoadingOverlay loading />;
263 showMigrate={!displayEmptyState}
270 className="f6 mono gray4 mb4 us-none pointer">
271 {abbreviateAddress(address)}
275 {incomingPoints.map(({ value }) => (
280 setPointCursor(Just(value));
281 push(names.ACCEPT_TRANSFER);
283 reject={() => addRejectedPoint(value)}
287 {displayEmptyState && (
288 <Grid.Item full as={Box} className="empty-points-container">
289 <Text className="empty-points-text">No Urbit IDs here yet</Text>
293 {outgoingPoints.length > 0 && (
294 <Grid.Item full as={Grid} gap={1} className="mb6">
295 <Grid.Item full as={H5}>
297 outgoingPoints.length,
305 points={outgoingPoints}
306 actions={(point: Point, i: number) => (
312 setPointCursor(Just(point.value));
313 // TODO: deep linking to fix this duplicate route
314 push(names.CANCEL_TRANSFER);
324 {allPoints.length > 0 && (
325 <Grid.Item full as={Grid} gap={1}>
326 <Grid.Item full as={PointList} points={allPoints} />
330 {lockedPoints.length > 0 && (
331 <Grid.Item full as={Grid} gap={1}>
332 <Grid.Item full as={PointList} locked points={lockedPoints} />
336 {showMigrateNotice && (
337 <Grid.Item full as={Box} className={'migrated-info-box'}>
338 <span className={'icon'}>
341 <span className={'message'}>
342 Not seeing a recently-migrated Layer 2 point? It may take a few
343 minutes for the public roller to be aware of the transaction.
350 {isEclipticOwner && (
355 detail="You have the authority to create a new Galaxy."
356 onClick={goCreateGalaxy}>
365 title={'Locked Stars'}
366 subtitle={`${starReleaseDetails.value.withdrawn} of ${starReleaseDetails.value.total} available`}
367 onClick={goStarRelease}></StarReleaseButton>
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