1import React, { useCallback, useEffect, useMemo } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, H5, HelpText, LinkButton, Flex } from 'indigo-react';
4import { get } from 'lodash';
6import { useHistory } from 'store/history';
7import { useWallet } from 'store/wallet';
8import { usePointCache } from 'store/pointCache';
9import { usePointCursor } from 'store/pointCursor';
10import { useStarReleaseCache } from 'store/starRelease';
11import { useNetwork } from 'store/network';
12import { useRollerStore } from 'store/rollerStore';
14import * as need from 'lib/need';
15import { isZeroAddress, abbreviateAddress } from 'lib/utils/address';
16import useIsEclipticOwner from 'lib/useIsEclipticOwner';
17import { useSyncDetails } from 'lib/useSyncPoints';
18import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
19import { pluralize } from 'lib/pluralize';
20import newGithubIssueUrl from 'new-github-issue-url';
22import View from 'components/View';
23import Passport from 'components/Passport';
24import Footer from 'components/Footer';
25import { ForwardButton } from 'components/Buttons';
26import CopiableAddress from 'components/copiable/CopiableAddress';
27import NavHeader from 'components/NavHeader';
28import L2PointHeader from 'components/L2/Headers/L2PointHeader';
29import IncomingPoint from 'components/L2/Points/IncomingPoint';
30import LoadingOverlay from 'components/L2/LoadingOverlay';
31import { onlyUnique } from 'lib/utils/array';
33export const maybeGetResult = (obj, key, defaultValue) =>
35 Nothing: () => defaultValue,
38 Ok: r => get(r.value, key, defaultValue),
39 Error: e => defaultValue,
43export const hasTransferProxy = details =>
44 !isZeroAddress(details.transferProxy);
46export const getOutgoingPoints = (controlledPoints, getDetails) => {
47 return controlledPoints
50 Error: () => Nothing(),
52 const points = c.value.ownedPoints.map(point =>
53 getDetails(point).chain(details =>
54 Just({ point: point, has: hasTransferProxy(details) })
57 if (points.every(p => Just.hasInstance(p))) {
58 const outgoing = points
59 .filter(p => p.value.has)
60 .map(p => p.value.point);
61 return Just(outgoing);
71export const isLocked = (details, contracts) =>
72 details.owner === contracts.linearSR ||
73 details.owner === contracts.conditionalSR;
75const PointList = function({
82 const { setPointCursor } = usePointCursor();
83 const { push, names } = useHistory();
86 <Grid gap={4} className={className}>
87 {points.map((point, i) => (
90 className={`full fourth-${(i % 4) + 1}-md fourth-${(i % 4) + 1}-lg`}>
99 setPointCursor(Just(point));
106 <Flex.Item className="mt2">{actions(point, i)}</Flex.Item>
115function ActionButtons({ actions = [] }) {
118 {actions.map(action => (
123 onClick={action.onClick}>
131export default function Points() {
132 const { wallet } = useWallet();
133 const { pop, push, popAndPush, names } = useHistory();
134 const { setPointCursor } = usePointCursor();
135 const { controlledPoints, getDetails } = usePointCache();
136 const { contracts } = useNetwork();
137 const isEclipticOwner = useIsEclipticOwner();
141 ] = useRejectedIncomingPointTransfers();
142 const { pointList } = useRollerStore();
143 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
145 const outgoingPoints = useMemo(
146 () => pointList.filter(({ isTransferProxySet }) => isTransferProxySet),
150 const incomingPoints = useMemo(
153 ({ isTransferProxy, value }) =>
154 isTransferProxy && !rejectedPoints.includes(value)
156 [pointList, rejectedPoints]
159 const _contracts = need.contracts(contracts);
161 const maybeLockedPoints = useMemo(
163 controlledPoints.chain(points =>
165 Error: () => Nothing(),
167 const points = c.value.ownedPoints.map(point =>
168 getDetails(point).chain(details =>
169 Just({ point, has: isLocked(details, _contracts) })
172 // if we have details for every point,
173 // return the array of pending transfers.
174 if (points.every(p => Just.hasInstance(p))) {
175 const locked = points
176 .filter(p => p.value.has)
177 .map(p => p.value.point);
185 [getDetails, controlledPoints, _contracts]
187 // if we can only interact with a single point, forget about the existence
188 // of this page and jump to the point page.
189 // if there are any pending transfers, incoming or outgoing, stay on this
190 // page, because those can only be completed/cancelled here.
192 if (Nothing.hasInstance(starReleaseDetails)) {
195 controlledPoints.matchWith({
202 ...c.value.ownedPoints,
203 ...c.value.votingPoints,
204 ...c.value.managingPoints,
205 ...c.value.spawningPoints,
209 incomingPoints.length === 0 &&
210 outgoingPoints.length === 0 &&
211 (starReleaseDetails.value === null ||
212 starReleaseDetails.value.total === 0)
214 setPointCursor(Just(all[0]));
215 popAndPush(names.POINT);
232 const address = need.addressFromWallet(wallet);
234 const loading = Nothing.hasInstance(controlledPoints);
236 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
237 const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
238 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
239 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
240 const lockedPoints = maybeLockedPoints.getOrElse([]);
243 ...ownedPoints.filter(
244 p => !outgoingPoints.find(({ value }) => value === p)
250 .sort((a, b) => Number(a) - Number(b))
253 const displayEmptyState =
254 !loading && incomingPoints.length === 0 && allPoints.length === 0;
256 const starReleasing = starReleaseDetails
257 .map(s => (s ? s.total > 0 : false))
261 syncStarReleaseDetails();
262 }, [syncStarReleaseDetails]);
266 'Notification' in window &&
267 Notification.permission !== 'denied' &&
268 Notification.permission !== 'granted'
270 Notification.requestPermission();
272 }, []); // eslint-disable-line react-hooks/exhaustive-deps
274 // sync display details for known points
275 useSyncDetails(ownedPoints);
277 const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
282 const goStarRelease = useCallback(() => push(names.STAR_RELEASE), [
287 // if we got an error result, we should display it, instead of showing
288 // a potentially inaccurately empty point list.
291 Just.hasInstance(controlledPoints) &&
292 controlledPoints.value.matchWith({
297 const url = newGithubIssueUrl({
300 title: controlledPoints.value.value,
301 body: `<!-- Please provide some context. Do you have Metamask installed? What login method did you use? -->`,
306 <Grid.Item full as={HelpText} className="mt8 t-center">
307 {controlledPoints.value.value}
309 Try reloading the page. If this problem persists, please{' '}
310 <a href={url}>file an issue on GitHub</a>.
319 (allPoints.length === 1 &&
320 incomingPoints.length === 0 &&
321 outgoingPoints.length === 0 &&
326 <LoadingOverlay loading />
336 header={<L2PointHeader hideHome hideTimer hideInvites showMigrate />}>
340 className="f6 mono gray4 mb4 us-none pointer">
341 {abbreviateAddress(address)}
344 {incomingPoints.map(({ value }) => (
349 setPointCursor(Just(value));
350 push(names.ACCEPT_TRANSFER);
352 reject={() => addRejectedPoint(value)}
356 {displayEmptyState && (
357 <Grid.Item full as={HelpText} className="mt8 t-center">
358 No points to display. This wallet is not the owner or proxy for any
363 {outgoingPoints.length > 0 && (
364 <Grid.Item full as={Grid} gap={1} className="mb6">
365 <Grid.Item full as={H5}>
367 outgoingPoints.length,
375 points={outgoingPoints.map(({ value }) => value)}
376 actions={(point, i) => (
382 setPointCursor(Just(point));
383 // TODO: deep linking to fix this duplicate route
384 push(names.CANCEL_TRANSFER);
394 {allPoints.length > 0 && (
395 <Grid.Item full as={Grid} gap={1}>
396 <Grid.Item full as={PointList} points={allPoints} />
400 {lockedPoints.length > 0 && (
401 <Grid.Item full as={Grid} gap={1}>
402 <Grid.Item full as={PointList} locked points={allPoints} />
409 {isEclipticOwner && (
414 detail="You have the authority to create a new Galaxy."
415 onClick={goCreateGalaxy}>
426 detail="You have points being released"
427 onClick={goStarRelease}>
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