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';
12import * as need from 'lib/need';
13import { isZeroAddress, abbreviateAddress } from 'lib/utils/address';
14import useIsEclipticOwner from 'lib/useIsEclipticOwner';
15import { useSyncDetails } from 'lib/useSyncPoints';
16import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
17import { pluralize } from 'lib/pluralize';
18import newGithubIssueUrl from 'new-github-issue-url';
20import View from 'components/View';
21import Blinky from 'components/Blinky';
22import Passport from 'components/Passport';
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 { useNetwork } from 'store/network';
31export const maybeGetResult = (obj, key, defaultValue) =>
33 Nothing: () => defaultValue,
36 Ok: r => get(r.value, key, defaultValue),
37 Error: e => defaultValue,
41export const hasTransferProxy = details =>
42 !isZeroAddress(details.transferProxy);
44export const getOutgoingPoints = (controlledPoints, getDetails) => {
45 return controlledPoints
48 Error: () => Nothing(),
50 const points = c.value.ownedPoints.map(point =>
51 getDetails(point).chain(details =>
52 Just({ point: point, has: hasTransferProxy(details) })
55 if (points.every(p => Just.hasInstance(p))) {
56 const outgoing = points
57 .filter(p => p.value.has)
58 .map(p => p.value.point);
59 return Just(outgoing);
69export const isLocked = (details, contracts) =>
70 details.owner === contracts.linearSR ||
71 details.owner === contracts.conditionalSR;
73const PointList = function({
80 const { setPointCursor } = usePointCursor();
81 const { push, names } = useHistory();
84 <Grid gap={4} className={className}>
85 {points.map((point, i) => (
88 className={`full fourth-${(i % 4) + 1}-md fourth-${(i % 4) + 1}-lg`}>
97 setPointCursor(Just(point));
104 <Flex.Item className="mt2">{actions(point, i)}</Flex.Item>
113function ActionButtons({ actions = [] }) {
116 {actions.map(action => (
121 onClick={action.onClick}>
129export default function Points() {
130 const { wallet } = useWallet();
131 const { pop, push, popAndPush, names } = useHistory();
132 const { setPointCursor } = usePointCursor();
133 const { controlledPoints, getDetails } = usePointCache();
134 const { contracts } = useNetwork();
135 const isEclipticOwner = useIsEclipticOwner();
139 ] = useRejectedIncomingPointTransfers();
140 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
142 const outgoingPoints = useMemo(
143 () => getOutgoingPoints(controlledPoints, getDetails),
144 [getDetails, controlledPoints]
147 const _contracts = need.contracts(contracts);
149 const maybeLockedPoints = useMemo(
151 controlledPoints.chain(points =>
153 Error: () => Nothing(),
155 const points = c.value.ownedPoints.map(point =>
156 getDetails(point).chain(details =>
157 Just({ point, has: isLocked(details, _contracts) })
160 // if we have details for every point,
161 // return the array of pending transfers.
162 if (points.every(p => Just.hasInstance(p))) {
163 const locked = points
164 .filter(p => p.value.has)
165 .map(p => p.value.point);
173 [getDetails, controlledPoints, _contracts]
176 // if we can only interact with a single point, forget about the existence
177 // of this page and jump to the point page.
178 // if there are any pending transfers, incoming or outgoing, stay on this
179 // page, because those can only be completed/cancelled here.
181 if (outgoingPoints.length < 1 || Nothing.hasInstance(starReleaseDetails)) {
184 controlledPoints.matchWith({
191 ...c.value.ownedPoints,
192 ...c.value.votingPoints,
193 ...c.value.managingPoints,
194 ...c.value.spawningPoints,
196 const incoming = c.value.incomingPoints.filter(
197 p => !rejectedPoints.includes(p)
201 incoming.length === 0 &&
202 outgoingPoints.length === 0 &&
203 (starReleaseDetails.value === null ||
204 starReleaseDetails.value.total === 0)
206 setPointCursor(Just(all[0]));
207 popAndPush(names.POINT);
223 const address = need.addressFromWallet(wallet);
225 const loading = Nothing.hasInstance(controlledPoints);
227 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
228 const incomingPoints = maybeGetResult(
232 ).filter(point => !rejectedPoints.includes(point));
233 const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
234 const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
235 const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
236 const lockedPoints = maybeLockedPoints.getOrElse([]);
239 ...ownedPoints.filter(p => !outgoingPoints.includes(p)),
243 ].sort((a, b) => Number(a) - Number(b));
245 const displayEmptyState =
246 !loading && incomingPoints.length === 0 && allPoints.length === 0;
248 const starReleasing = starReleaseDetails
249 .map(s => (s ? s.total > 0 : false))
253 syncStarReleaseDetails();
254 }, [syncStarReleaseDetails]);
256 // sync display details for known points
257 useSyncDetails(ownedPoints);
259 const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
264 const goStarRelease = useCallback(() => push(names.STAR_RELEASE), [
269 // if we got an error result, we should display it, instead of showing
270 // a potentially inaccurately empty point list.
273 Just.hasInstance(controlledPoints) &&
274 controlledPoints.value.matchWith({
279 const url = newGithubIssueUrl({
282 title: controlledPoints.value.value,
283 body: `<!-- Please provide some context. Do you have Metamask installed? What login method did you use? -->`,
288 <Grid.Item full as={HelpText} className="mt8 t-center">
289 {controlledPoints.value.value}
291 Try reloading the page. If this problem persists, please{' '}
292 <a href={url}>file an issue on GitHub</a>.
301 (allPoints.length === 1 &&
302 incomingPoints.length === 0 &&
303 outgoingPoints.length === 0 &&
309 <Grid.Item full as={HelpText} className="mt8 t-center">
310 <Blinky /> Loading...
322 header={<L2PointHeader hideTimer hideInvites showMigrate />}>
326 className="f6 mono gray4 mb4 us-none pointer">
327 {abbreviateAddress(address)}
330 {incomingPoints.map(point => (
334 setPointCursor(Just(point));
335 push(names.ACCEPT_TRANSFER);
337 reject={() => addRejectedPoint(point)}
341 {displayEmptyState && (
342 <Grid.Item full as={HelpText} className="mt8 t-center">
343 No points to display. This wallet is not the owner or proxy for any
348 {outgoingPoints.length > 0 && (
349 <Grid.Item full as={Grid} gap={1} className="mb6">
350 <Grid.Item full as={H5}>
352 outgoingPoints.length,
360 points={outgoingPoints}
361 actions={(point, i) => (
367 setPointCursor(Just(point));
368 // TODO: deep linking to fix this duplicate route
369 push(names.CANCEL_TRANSFER);
379 {allPoints.length > 0 && (
380 <Grid.Item full as={Grid} gap={1}>
381 <Grid.Item full as={PointList} points={allPoints} />
385 {lockedPoints.length > 0 && (
386 <Grid.Item full as={Grid} gap={1}>
387 <Grid.Item full as={PointList} locked points={allPoints} />
394 {isEclipticOwner && (
399 detail="You have the authority to create a new Galaxy."
400 onClick={goCreateGalaxy}>
411 detail="You have points being released"
412 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