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';
32export const maybeGetResult = (obj, key, defaultValue) =>
34 Nothing: () => defaultValue,
37 Ok: r => get(r.value, key, defaultValue),
38 Error: e => defaultValue,
42export const hasTransferProxy = details =>
43 !isZeroAddress(details.transferProxy);
45export const isLocked = (details, contracts) =>
46 details.owner === contracts.linearSR ||
47 details.owner === contracts.conditionalSR;
49const PointList = function({
57 const { setPointCursor } = usePointCursor();
58 const { push, names } = useHistory();
61 <Grid gap={4} className={className}>
62 {points.map((point, i) => (
65 className={`full fourth-${(i % 4) + 1}-md fourth-${(i % 4) + 1}-lg`}>
69 processing={processing}
75 setPointCursor(Just(point));
82 <Flex.Item className="mt2">{actions(point, i)}</Flex.Item>
91function ActionButtons({ actions = [] }) {
94 {actions.map(action => (
99 onClick={action.onClick}>
107export default function Points() {
108 const { wallet } = useWallet();
109 const { pop, push, popAndPush, names } = useHistory();
110 const { setPointCursor } = usePointCursor();
111 const { controlledPoints, getDetails } = usePointCache();
112 const { contracts } = useNetwork();
113 const isEclipticOwner = useIsEclipticOwner();
117 ] = useRejectedIncomingPointTransfers();
118 const { pointList } = useRollerStore();
119 const { syncStarReleaseDetails, starReleaseDetails } = useStarReleaseCache();
121 const outgoingPoints = useMemo(
124 ({ isTransferProxySet, isTransferProxy }) =>
125 isTransferProxySet && !isTransferProxy
130 const incomingPoints = useMemo(
133 ({ isTransferProxy, value }) =>
134 isTransferProxy && !rejectedPoints.includes(value)
136 [pointList, rejectedPoints]
139 const _contracts = need.contracts(contracts);
141 const maybeLockedPoints = useMemo(
143 controlledPoints.chain(points =>
145 Error: () => Nothing(),
147 const points = c.value.ownedPoints.map(point =>
148 getDetails(point).chain(details =>
149 Just({ point, has: isLocked(details, _contracts) })
152 // if we have details for every point,
153 // return the array of pending transfers.
154 if (points.every(p => Just.hasInstance(p))) {
155 const locked = points
156 .filter(p => p.value.has)
157 .map(p => p.value.point);
165 [getDetails, controlledPoints, _contracts]
167 // if we can only interact with a single point, forget about the existence
168 // of this page and jump to the point page.
169 // if there are any pending transfers, incoming or outgoing, stay on this
170 // page, because those can only be completed/cancelled here.
172 if (Nothing.hasInstance(starReleaseDetails)) {
175 controlledPoints.matchWith({
182 ...c.value.ownedPoints,
183 ...c.value.votingPoints,
184 ...c.value.managingPoints,
185 ...c.value.spawningPoints,
189 incomingPoints.length === 0 &&
190 outgoingPoints.length === 0 &&
191 (starReleaseDetails.value === null ||
192 starReleaseDetails.value.total === 0)
194 setPointCursor(Just(all[0]));
195 popAndPush(names.POINT);
212 const address = need.addressFromWallet(wallet);
214 const loading = Nothing.hasInstance(controlledPoints);
216 const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
217 const lockedPoints = maybeLockedPoints.getOrElse([]);
219 const allPoints = pointList
221 ({ isTransferProxySet, shouldDisplay }) =>
222 !isTransferProxySet && shouldDisplay
224 .map(({ value }) => value);
226 const processingPoints = pointList
227 .filter(({ shouldDisplay }) => !shouldDisplay)
228 .map(({ value }) => value);
230 const displayEmptyState =
231 !loading && incomingPoints.length === 0 && allPoints.length === 0;
233 const starReleasing = starReleaseDetails
234 .map(s => (s ? s.total > 0 : false))
238 syncStarReleaseDetails();
239 }, [syncStarReleaseDetails]);
243 'Notification' in window &&
244 Notification.permission !== 'denied' &&
245 Notification.permission !== 'granted'
247 Notification.requestPermission();
249 }, []); // eslint-disable-line react-hooks/exhaustive-deps
251 // sync display details for known points
252 useSyncDetails(ownedPoints);
254 const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
259 const goStarRelease = useCallback(() => push(names.STAR_RELEASE), [
264 // if we got an error result, we should display it, instead of showing
265 // a potentially inaccurately empty point list.
268 Just.hasInstance(controlledPoints) &&
269 controlledPoints.value.matchWith({
274 const url = newGithubIssueUrl({
277 title: controlledPoints.value.value,
278 body: `<!-- Please provide some context. Do you have Metamask installed? What login method did you use? -->`,
283 <Grid.Item full as={HelpText} className="mt8 t-center">
284 {controlledPoints.value.value}
286 Try reloading the page. If this problem persists, please{' '}
287 <a href={url}>file an issue on GitHub</a>.
296 (allPoints.length === 1 &&
297 incomingPoints.length === 0 &&
298 outgoingPoints.length === 0 &&
303 <LoadingOverlay loading />
313 header={<L2PointHeader hideHome hideTimer hideInvites showMigrate />}>
317 className="f6 mono gray4 mb4 us-none pointer">
318 {abbreviateAddress(address)}
321 {incomingPoints.map(({ value }) => (
326 setPointCursor(Just(value));
327 push(names.ACCEPT_TRANSFER);
329 reject={() => addRejectedPoint(value)}
333 {displayEmptyState && (
334 <Grid.Item full as={HelpText} className="mt8 t-center">
335 No points to display. This wallet is not the owner or proxy for any
340 {outgoingPoints.length > 0 && (
341 <Grid.Item full as={Grid} gap={1} className="mb6">
342 <Grid.Item full as={H5}>
344 outgoingPoints.length,
352 points={outgoingPoints.map(({ value }) => value)}
353 actions={(point, i) => (
359 setPointCursor(Just(point));
360 // TODO: deep linking to fix this duplicate route
361 push(names.CANCEL_TRANSFER);
371 {allPoints.length > 0 && (
372 <Grid.Item full as={Grid} gap={1}>
373 <Grid.Item full as={PointList} points={allPoints} />
377 {lockedPoints.length > 0 && (
378 <Grid.Item full as={Grid} gap={1}>
379 <Grid.Item full as={PointList} locked points={allPoints} />
383 {processingPoints.length > 0 && (
384 <Grid.Item full as={Grid} gap={1} className="mv6">
385 <Grid.Item full as={H5}>
391 points={processingPoints}
400 {isEclipticOwner && (
405 detail="You have the authority to create a new Galaxy."
406 onClick={goCreateGalaxy}>
417 detail="You have points being released"
418 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