on master help sign in
Invites working fairly well
4002714Will Galebach
src/components/InlineEthereumTransaction.js+42−12
@@ -28,6 +28,8 @@ import { convertToInt } from 'lib/convertToInt';
28import NeedFundsNotice from './NeedFundsNotice';
28import NeedFundsNotice from './NeedFundsNotice';
29import NoticeBox from './NoticeBox';
29import NoticeBox from './NoticeBox';
30
30
31+import './InlineEthereumTransaction.scss';
32+
31export default function InlineEthereumTransaction({
33export default function InlineEthereumTransaction({
32 // from useEthereumTransaction.bind
34 // from useEthereumTransaction.bind
33 initializing,
35 initializing,
@@ -101,7 +103,12 @@ export default function InlineEthereumTransaction({
101 const renderPrimarySection = () => {
103 const renderPrimarySection = () => {
102 if (error) {
104 if (error) {
103 return (
105 return (
106+ <Grid.Item
107+ className="eth-tx-button"
108+ full
104− <Grid.Item full as={RestartButton} solid onClick={() => reset()}>
109+ as={RestartButton}
110+ solid
111+ onClick={() => reset()}>
105 Reset Transaction
112 Reset Transaction
106 </Grid.Item>
113 </Grid.Item>
107 );
114 );
@@ -109,7 +116,11 @@ export default function InlineEthereumTransaction({
109 return (
116 return (
110 <>
117 <>
111 <Grid.Divider />
118 <Grid.Divider />
119+ <Grid.Item
120+ className="eth-tx-button"
121+ full
112− <Grid.Item full as={RestartButton} onClick={onReturn}>
122+ as={RestartButton}
123+ onClick={onReturn}>
113 Return
124 Return
114 </Grid.Item>
125 </Grid.Item>
115 <Grid.Divider />
126 <Grid.Divider />
@@ -134,6 +145,7 @@ export default function InlineEthereumTransaction({
134 } else if (showBroadcastButton) {
145 } else if (showBroadcastButton) {
135 return (
146 return (
136 <Grid.Item
147 <Grid.Item
148+ className="eth-tx-button"
137 full
149 full
138 as={ForwardButton}
150 as={ForwardButton}
139 solid
151 solid
@@ -146,6 +158,7 @@ export default function InlineEthereumTransaction({
146 } else if (showLoadingButton) {
158 } else if (showLoadingButton) {
147 return (
159 return (
148 <Grid.Item
160 <Grid.Item
161+ className="eth-tx-button"
149 full
162 full
150 as={ProgressButton}
163 as={ProgressButton}
151 success
164 success
@@ -157,6 +170,7 @@ export default function InlineEthereumTransaction({
157 } else {
170 } else {
158 return (
171 return (
159 <Grid.Item
172 <Grid.Item
173+ className="eth-tx-button"
160 full
174 full
161 as={GenerateButton}
175 as={GenerateButton}
162 onClick={generateAndSign}
176 onClick={generateAndSign}
@@ -207,13 +221,15 @@ export default function InlineEthereumTransaction({
207 {renderPrimarySection()}
221 {renderPrimarySection()}
208
222
209 {error && (
223 {error && (
224+ <Grid.Item
210− <Grid.Item full as={ErrorText} className="mv1">
225+ className="eth-tx-button" full as={ErrorText} className="mv1">
211 {error.message}
226 {error.message}
212 </Grid.Item>
227 </Grid.Item>
213 )}
228 )}
214
229
215 {needFunds && (
230 {needFunds && (
216 <Grid.Item
231 <Grid.Item
232+ className="eth-tx-button"
217 full
233 full
218 as={NeedFundsNotice}
234 as={NeedFundsNotice}
219 className="mt3"
235 className="mt3"
@@ -224,6 +240,7 @@ export default function InlineEthereumTransaction({
224 {showConfigureInput && (
240 {showConfigureInput && (
225 <>
241 <>
226 <Grid.Item
242 <Grid.Item
243+ className="eth-tx-button"
227 full
244 full
228 as={ToggleInput}
245 as={ToggleInput}
229 name="useAdvanced"
246 name="useAdvanced"
@@ -234,9 +251,11 @@ export default function InlineEthereumTransaction({
234 />
251 />
235
252
236 <Condition when="useAdvanced" is={true}>
253 <Condition when="useAdvanced" is={true}>
254+ <Grid.Item
237− <Grid.Item full className="mb2"></Grid.Item>
255+ className="eth-tx-button" full className="mb2"></Grid.Item>
238 <Grid.Divider />
256 <Grid.Divider />
239 <Grid.Item
257 <Grid.Item
258+ className="eth-tx-button"
240 full
259 full
241 as={Flex}
260 as={Flex}
242 row
261 row
@@ -255,6 +274,7 @@ export default function InlineEthereumTransaction({
255
274
256 {/* TODO(shrugs): move to indigo/RangeInput */}
275 {/* TODO(shrugs): move to indigo/RangeInput */}
257 <Grid.Item
276 <Grid.Item
277+ className="eth-tx-button"
258 full
278 full
259 as="input"
279 as="input"
260 type="range"
280 type="range"
@@ -266,6 +286,7 @@ export default function InlineEthereumTransaction({
266 }
286 }
267 />
287 />
268 <Grid.Item
288 <Grid.Item
289+ className="eth-tx-button"
269 full
290 full
270 as={Flex}
291 as={Flex}
271 row
292 row
@@ -281,7 +302,8 @@ export default function InlineEthereumTransaction({
281
302
282 {showSignedTx && fakeSigned && (
303 {showSignedTx && fakeSigned && (
283 <>
304 <>
305+ <Grid.Item
284− <Grid.Item full as={NoticeBox} className="mt2">
306+ className="eth-tx-button" full as={NoticeBox} className="mt2">
285 Your wallet will sign the transaction upon sending it.
307 Your wallet will sign the transaction upon sending it.
286 </Grid.Item>
308 </Grid.Item>
287 </>
309 </>
@@ -290,6 +312,7 @@ export default function InlineEthereumTransaction({
290 {showSignedTx && !fakeSigned && (
312 {showSignedTx && !fakeSigned && (
291 <>
313 <>
292 <Grid.Item
314 <Grid.Item
315+ className="eth-tx-button"
293 full
316 full
294 as={ToggleInput}
317 as={ToggleInput}
295 name="viewSigned"
318 name="viewSigned"
@@ -317,21 +340,25 @@ function SignedTransactionList({ serializedTxsHex, nonce, maxCost }) {
317 return serializedTxsHex.map((serializedTxHex, i) => (
340 return serializedTxsHex.map((serializedTxHex, i) => (
318 <React.Fragment key={i}>
341 <React.Fragment key={i}>
319 <Grid.Divider />
342 <Grid.Divider />
343+ <Grid.Item
320− <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
344+ className="eth-tx-button" full as={Flex} justify="between" className="pv4 black f5">
321 <Flex.Item>Nonce</Flex.Item>
345 <Flex.Item>Nonce</Flex.Item>
322 <Flex.Item>{nonce + i}</Flex.Item>
346 <Flex.Item>{nonce + i}</Flex.Item>
323 </Grid.Item>
347 </Grid.Item>
324 <Grid.Divider />
348 <Grid.Divider />
349+ <Grid.Item
325− <Grid.Item full as={Flex} justify="between" className="pv4 black f5">
350+ className="eth-tx-button" full as={Flex} justify="between" className="pv4 black f5">
326 <Flex.Item>Transaction Cost</Flex.Item>
351 <Flex.Item>Transaction Cost</Flex.Item>
327 <Flex.Item>{maxCost} ETH</Flex.Item>
352 <Flex.Item>{maxCost} ETH</Flex.Item>
328 </Grid.Item>
353 </Grid.Item>
329 <Grid.Divider />
354 <Grid.Divider />
355+ <Grid.Item
330− <Grid.Item full as={Flex} justify="between" className="mt3 mb2">
356+ className="eth-tx-button" full as={Flex} justify="between" className="mt3 mb2">
331 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
357 <Flex.Item as={H5}>Signed Transaction Hex</Flex.Item>
332 <Flex.Item as={CopyButton} text={serializedTxHex} />
358 <Flex.Item as={CopyButton} text={serializedTxHex} />
333 </Grid.Item>
359 </Grid.Item>
360+ <Grid.Item
334− <Grid.Item full as="code" className="mb4 f6 mono gray4 wrap">
361+ className="eth-tx-button" full as="code" className="mb4 f6 mono gray4 wrap">
335 {serializedTxHex}
362 {serializedTxHex}
336 </Grid.Item>
363 </Grid.Item>
337 <Grid.Divider />
364 <Grid.Divider />
@@ -343,18 +370,21 @@ function TransactionReceipt({ txHashes, finalCost, onClose }) {
343 const txUrls = useExploreTxUrls(txHashes);
370 const txUrls = useExploreTxUrls(txHashes);
344 return (
371 return (
345 <>
372 <>
373+ <Grid.Item
346− <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
374+ className="eth-tx-button" full as={Flex} justify="between" className="pv2 black f5">
347 <Flex.Item>Receipt</Flex.Item>
375 <Flex.Item>Receipt</Flex.Item>
348 <Flex.Item onClick={onClose} className="underline pointer">
376 <Flex.Item onClick={onClose} className="underline pointer">
349 Close
377 Close
350 </Flex.Item>
378 </Flex.Item>
351 </Grid.Item>
379 </Grid.Item>
380+ <Grid.Item
352− <Grid.Item full as={Flex} justify="between" className="pv2 black f5">
381+ className="eth-tx-button" full as={Flex} justify="between" className="pv2 black f5">
353 <Flex.Item>Transaction Cost</Flex.Item>
382 <Flex.Item>Transaction Cost</Flex.Item>
354 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
383 <Flex.Item className="mono">{finalCost} ETH</Flex.Item>
355 </Grid.Item>
384 </Grid.Item>
356 <Grid.Divider />
385 <Grid.Divider />
386+ <Grid.Item
357− <Grid.Item full as={Flex} col className="pv4">
387+ className="eth-tx-button" full as={Flex} col className="pv4">
358 <Flex.Item as={H5}>Receipt</Flex.Item>
388 <Flex.Item as={H5}>Receipt</Flex.Item>
359
389
360 {txHashes &&
390 {txHashes &&
src/components/InlineEthereumTransaction.scssnew file+5
@@ -0,0 +1,5 @@
1+@import 'style/shared/variables.scss';
2+
3+.eth-tx-button {
4+ border-radius: $medium-border-radius;
5+}
src/components/L2/Dropdowns/AccountsDropdown.tsx+5−1
@@ -9,6 +9,7 @@ import { useWallet } from 'store/wallet';
9import { useHistory } from 'store/history';
9import { useHistory } from 'store/history';
10import { usePointCursor } from 'store/pointCursor';
10import { usePointCursor } from 'store/pointCursor';
11import { usePointCache } from 'store/pointCache';
11import { usePointCache } from 'store/pointCache';
12+import { clearInvitesStorage } from 'store/storage/roller';
12import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
13import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
13import Dropdown from './Dropdown';
14import Dropdown from './Dropdown';
14
15
@@ -39,7 +40,10 @@ const AccountsDropdown = () => {
39 };
40 };
40 const goToMigrate = () => push(names.MIGRATE);
41 const goToMigrate = () => push(names.MIGRATE);
41 const goToBitcoin = () => push(names.BITCOIN);
42 const goToBitcoin = () => push(names.BITCOIN);
42− const onLogout = reset;
43+ const onLogout = () => {
44+ clearInvitesStorage();
45+ reset();
46+ };
43
47
44 return (
48 return (
45 <Dropdown
49 <Dropdown
src/components/L2/Dropdowns/Dropdown.scss+3
@@ -31,12 +31,15 @@
31 top: 36px;
31 top: 36px;
32 left: -3px;
32 left: -3px;
33 border-radius: $medium-border-radius;
33 border-radius: $medium-border-radius;
34+ z-index: 1;
34
35
35 .content {
36 .content {
36 border-radius: $medium-border-radius;
37 border-radius: $medium-border-radius;
37 background-color: white;
38 background-color: white;
38 border: 1px solid $light-gray;
39 border: 1px solid $light-gray;
39 width: 240px;
40 width: 240px;
41+ max-height: 80vh;
42+ overflow-y: scroll;
40 }
43 }
41 }
44 }
42}
45}
src/components/L2/Headers/L2BackHeader.tsx+29−4
@@ -1,4 +1,4 @@
1−import React, { useCallback, useEffect } from 'react';
1+import React, { useCallback, useEffect, useState } from 'react';
2import { Row, Icon } from '@tlon/indigo-react';
2import { Row, Icon } from '@tlon/indigo-react';
3
3
4import useRoller from 'lib/useRoller';
4import useRoller from 'lib/useRoller';
@@ -6,15 +6,24 @@ import { useHistory } from 'store/history';
6import { useRollerStore } from 'store/roller';
6import { useRollerStore } from 'store/roller';
7
7
8import './L2BackHeader.scss';
8import './L2BackHeader.scss';
9+import { useNetwork } from 'store/network';
10+import { useWallet } from 'store/wallet';
11+import { toBN } from 'web3-utils';
12+import BN from 'bn.js';
9
13
10export interface L2BackHeaderProps {
14export interface L2BackHeaderProps {
11 back?: () => void;
15 back?: () => void;
16+ hideBalance?: boolean;
12}
17}
13
18
14−const L2BackHeader = ({ back }: L2BackHeaderProps) => {
19+const L2BackHeader = ({ back, hideBalance = false }: L2BackHeaderProps) => {
15 const { config } = useRoller();
20 const { config } = useRoller();
16 const { nextRoll, currentL2 } = useRollerStore(store => store);
21 const { nextRoll, currentL2 } = useRollerStore(store => store);
17 const { pop }: any = useHistory();
22 const { pop }: any = useHistory();
23+ const { wallet }: any = useWallet();
24+ const { web3 }: any = useNetwork();
25+
26+ const [ethBalance, setEthBalance] = useState<BN>(toBN(0));
18
27
19 const goBack = useCallback(() => {
28 const goBack = useCallback(() => {
20 if (back) {
29 if (back) {
@@ -28,7 +37,21 @@ const L2BackHeader = ({ back }: L2BackHeaderProps) => {
28 console.log('loaded config in L2BackHeader:', config);
37 console.log('loaded config in L2BackHeader:', config);
29 }, [config]);
38 }, [config]);
30
39
31− const ethBalance = 0.032;
40+ useEffect(() => {
41+ const getEthBalance = async () => {
42+ const _web3 = web3.getOrElse(null);
43+ const _wallet = wallet.getOrElse(null);
44+
45+ if (_web3 && _wallet) {
46+ const newBalance = toBN(await _web3.eth.getBalance(_wallet.address));
47+ setEthBalance(newBalance);
48+ }
49+ };
50+
51+ if (!currentL2) {
52+ getEthBalance();
53+ }
54+ }, [currentL2, setEthBalance, wallet, web3]);
32
55
33 return (
56 return (
34 <Row className="l2-back-header">
57 <Row className="l2-back-header">
@@ -39,7 +62,9 @@ const L2BackHeader = ({ back }: L2BackHeaderProps) => {
39 {nextRoll}
62 {nextRoll}
40 </div>
63 </div>
41 ) : (
64 ) : (
65+ !hideBalance && (
42− <div className="eth-balance">Balance: {ethBalance} ETH</div>
66+ <div className="eth-balance">Balance: {Number(ethBalance)} ETH</div>
67+ )
43 )}
68 )}
44 </Row>
69 </Row>
45 );
70 );
src/components/L2/Paginator.scss+1−1
@@ -6,9 +6,9 @@
6 justify-content: center;
6 justify-content: center;
7 align-items: center;
7 align-items: center;
8 width: 100%;
8 width: 100%;
9+ margin-top: 8px;
9
10
10 .info {
11 .info {
11− margin-top: 8px;
12 width: 40px;
12 width: 40px;
13 text-align: center;
13 text-align: center;
14 }
14 }
src/components/MiniBackButton.js+4−1
@@ -1,11 +1,14 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3+import { Icon } from '@tlon/indigo-react';
3import { IconButton } from 'indigo-react';
4import { IconButton } from 'indigo-react';
4
5
6+import './MiniBackButton.scss';
7+
5export const MiniBackButton = ({ className, isExit = false, ...rest }) => {
8export const MiniBackButton = ({ className, isExit = false, ...rest }) => {
6 return (
9 return (
7 <IconButton className={cn('black', className)} {...rest}>
10 <IconButton className={cn('black', className)} {...rest}>
8− {'<-'}
11+ <Icon icon="ChevronWest" className="back-button" />
9 </IconButton>
12 </IconButton>
10 );
13 );
11};
14};
src/components/MiniBackButton.scssnew file+10
@@ -0,0 +1,10 @@
1+@import 'style/shared/variables.scss';
2+
3+svg.back-button {
4+ padding: 6px 9px;
5+ width: 14px;
6+ height: 20px;
7+ background-color: $washedGray;
8+ border-radius: $medium-border-radius;
9+ cursor: pointer;
10+}
src/lib/useEthereumTransaction.js+4
@@ -144,14 +144,17 @@ export default function useEthereumTransaction(
144 );
144 );
145
145
146 setSignedTransactions(txns);
146 setSignedTransactions(txns);
147+ console.log(2, txns)
147
148
148 // Per return type of `signTransaction`, tx is either a Transaction or FakeSignedTx.
149 // Per return type of `signTransaction`, tx is either a Transaction or FakeSignedTx.
149 // We check the negation in this case, since TS cannot perform run-time check of type
150 // We check the negation in this case, since TS cannot perform run-time check of type
150 // FakeSignedTx, but it can check for an instance of the ethereumjs/tx class.
151 // FakeSignedTx, but it can check for an instance of the ethereumjs/tx class.
151 if (txns.some(tx => !(tx instanceof Transaction))) {
152 if (txns.some(tx => !(tx instanceof Transaction))) {
152 setState(STATE.FAKE_SIGNED);
153 setState(STATE.FAKE_SIGNED);
154+ console.log(3)
153 } else {
155 } else {
154 setState(STATE.SIGNED);
156 setState(STATE.SIGNED);
157+ console.log(4)
155 }
158 }
156 } catch (error) {
159 } catch (error) {
157 console.error('error signing txs', error);
160 console.error('error signing txs', error);
@@ -174,6 +177,7 @@ export default function useEthereumTransaction(
174
177
175 const broadcast = useCallback(async () => {
178 const broadcast = useCallback(async () => {
176 try {
179 try {
180+ console.log(5)
177 setConfirmationProgress(0.0);
181 setConfirmationProgress(0.0);
178 setError(undefined);
182 setError(undefined);
179 setState(STATE.BROADCASTED);
183 setState(STATE.BROADCASTED);
src/lib/useRoller.ts+52−12
@@ -3,6 +3,7 @@ import * as wg from 'lib/walletgen';
3import * as need from 'lib/need';
3import * as need from 'lib/need';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
5import * as ob from 'urbit-ob';
5import * as ob from 'urbit-ob';
6+import { Just, Nothing } from 'folktale/maybe';
6
7
7import {
8import {
8 Config,
9 Config,
@@ -24,9 +25,13 @@ import { usePointCursor } from 'store/pointCursor';
24import { useNetwork } from 'store/network';
25import { useNetwork } from 'store/network';
25import { signTransactionHash } from './authToken';
26import { signTransactionHash } from './authToken';
26import { Invite } from 'types/Invite';
27import { Invite } from 'types/Invite';
28+import {
29+ getStoredInvites,
30+ setPendingInvites,
31+ setStoredInvites,
27−import { getStoredInvites, setPendingInvites, setStoredInvites } from 'store/storage/roller';
32+} from 'store/storage/roller';
28import { usePointCache } from 'store/pointCache';
33import { usePointCache } from 'store/pointCache';
29−import { maybeGetResult } from 'views/Points';
34+import { hasTransferProxy, maybeGetResult } from 'views/Points';
30
35
31const hasPoint = (point: number) => (invite: Invite) => invite.planet === point;
36const hasPoint = (point: number) => (invite: Invite) => invite.planet === point;
32
37
@@ -50,11 +55,12 @@ const getProxyAndNonce = (
50 : { proxy: undefined, nonce: undefined };
55 : { proxy: undefined, nonce: undefined };
51
56
52export default function useRoller() {
57export default function useRoller() {
53− const { wallet, walletType, walletHdPath, authToken }: any = useWallet();
58+ const { wallet, authToken }: any = useWallet();
54 const { pointCursor }: any = usePointCursor();
59 const { pointCursor }: any = usePointCursor();
55 const { web3, contracts }: any = useNetwork();
60 const { web3, contracts }: any = useNetwork();
56 const allPoints: any = usePointCache();
61 const allPoints: any = usePointCache();
57 const controlledPoints = allPoints?.controlledPoints;
62 const controlledPoints = allPoints?.controlledPoints;
63+ const getDetails = allPoints?.getDetails;
58
64
59 const {
65 const {
60 nextBatchTime,
66 nextBatchTime,
@@ -62,6 +68,7 @@ export default function useRoller() {
62 setNextRoll,
68 setNextRoll,
63 setPendingTransactions,
69 setPendingTransactions,
64 setInvites,
70 setInvites,
71+ currentL2,
65 } = useRollerStore();
72 } = useRollerStore();
66 const [config, setConfig] = useState<Config | null>(null);
73 const [config, setConfig] = useState<Config | null>(null);
67
74
@@ -229,7 +236,7 @@ export default function useRoller() {
229
236
230 const stillPending = invites.pending.filter(invite => {
237 const stillPending = invites.pending.filter(invite => {
231 const completed = !pendingTransactions.find(
238 const completed = !pendingTransactions.find(
232− ({ tx }) => tx.tx?.data?.ship === invite.planet
239+ p => p?.rawTx?.tx?.tx?.data?.ship === invite.planet
233 );
240 );
234
241
235 if (completed) {
242 if (completed) {
@@ -246,12 +253,43 @@ export default function useRoller() {
246 });
253 });
247 setInvites(availableInvites);
254 setInvites(availableInvites);
248
255
256+ let possibleMissingInvites: number[] = [];
257+
258+ if (currentL2) {
249− const allSpawned = await api.getSpawned(Number(curPoint));
259+ const allSpawned = await api.getSpawned(Number(curPoint));
250− const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
260+ const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
251− const spawnedAndOwned = ownedPoints.filter(
261+ possibleMissingInvites = ownedPoints.filter(
262+ (p: number) =>
252− (p: number) => allSpawned.includes(p) && p > 2000
263+ allSpawned.includes(p) &&
264+ azimuth.azimuth.getPointSize(p) === azimuth.azimuth.PointSize.Planet
253− );
265+ );
254− console.log('SPAWNED AND OWNED', spawnedAndOwned);
266+ console.log('SPAWNED AND OWNED', possibleMissingInvites);
267+ } else {
268+ const maybeOutgoingPoints = controlledPoints.chain((points: any) =>
269+ points.matchWith({
270+ Error: () => Nothing(),
271+ Ok: (c: any) => {
272+ const points = c.value.ownedPoints.map((point: number) =>
273+ getDetails(point).chain((details: any) =>
274+ Just({ point: point, has: hasTransferProxy(details) })
275+ )
276+ );
277+ // if we have details for every point,
278+ // return the array of pending transfers.
279+ if (points.every((p: any) => Just.hasInstance(p))) {
280+ const outgoing = points
281+ .filter((p: any) => p.value.has)
282+ .map((p: any) => p.value.point);
283+ return Just(outgoing);
284+ } else {
285+ return Nothing();
286+ }
287+ },
288+ })
289+ );
290+
291+ possibleMissingInvites = maybeOutgoingPoints.getOrElse([]);
292+ }
255
293
256 // Iterate over all spawned and controlled planets
294 // Iterate over all spawned and controlled planets
257 // If the planet is not in available invites, generate the ticket and add it
295 // If the planet is not in available invites, generate the ticket and add it
@@ -259,8 +297,8 @@ export default function useRoller() {
259 const _contracts = contracts.getOrElse(null);
297 const _contracts = contracts.getOrElse(null);
260
298
261 if (_authToken && _contracts) {
299 if (_authToken && _contracts) {
262− for (let i = 0; i < spawnedAndOwned.length; i++) {
300+ for (let i = 0; i < possibleMissingInvites.length; i++) {
263− const point = spawnedAndOwned[i];
301+ const point = possibleMissingInvites[i];
264
302
265 if (!availableInvites.find(hasPoint(point))) {
303 if (!availableInvites.find(hasPoint(point))) {
266 console.log('MISSING IN AVAILABLE', point);
304 console.log('MISSING IN AVAILABLE', point);
@@ -300,6 +338,8 @@ export default function useRoller() {
300 authToken,
338 authToken,
301 contracts,
339 contracts,
302 controlledPoints,
340 controlledPoints,
341+ currentL2,
342+ getDetails,
303 ]);
343 ]);
304
344
305 // On load, get initial config
345 // On load, get initial config
src/store/lib/useInvitesStore.js−2
@@ -42,8 +42,6 @@ export default function useInvitesStore() {
42 )
42 )
43 );
43 );
44
44
45− console.log('INVITES STORE', availableInvites, invitedPoints, activity);
46−
47 const sentInvites = invitedPoints.length;
45 const sentInvites = invitedPoints.length;
48 const acceptedInvites = activity.filter(i => !!i).length;
46 const acceptedInvites = activity.filter(i => !!i).length;
49
47
src/store/storage/roller.ts+2−1
@@ -10,7 +10,6 @@ export interface StoredInvites {
10
10
11export const getStoredInvites = (point: string): StoredInvites => {
11export const getStoredInvites = (point: string): StoredInvites => {
12 const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
12 const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
13− console.log(invitesString)
14 const invites = JSON.parse(invitesString);
13 const invites = JSON.parse(invitesString);
15
14
16 console.log('STORED INVITES', invites);
15 console.log('STORED INVITES', invites);
@@ -34,3 +33,5 @@ export const setPendingInvites = (point: string, pendingInvites: Invite[]) => {
34 invites.pending.push(...pendingInvites);
33 invites.pending.push(...pendingInvites);
35 setStoredInvites(point, invites);
34 setStoredInvites(point, invites);
36};
35};
36+
37+export const clearInvitesStorage = () => localStorage.removeItem(INVITES_KEY);
src/views/Invite/Cohort.js+164−318
@@ -1,348 +1,146 @@
1−import React, { useMemo, useEffect, useCallback, useState } from 'react';
1+import React, { useEffect, useCallback, useState } from 'react';
2−import { Grid, Flex, Button } from 'indigo-react';
2+import { Grid, Button } from 'indigo-react';
3−import * as ob from 'urbit-ob';
4−import { Just, Nothing } from 'folktale/maybe';
5import cn from 'classnames';
3import cn from 'classnames';
4+import * as azimuth from 'azimuth-js';
5+import { Icon, Row, StatelessTextInput } from '@tlon/indigo-react';
6
6
7import { usePointCursor } from 'store/pointCursor';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
8import { usePointCache } from 'store/pointCache';
9import { useWallet } from 'store/wallet';
9import { useWallet } from 'store/wallet';
10−
10+import { useNetwork } from 'store/network';
11−import View from 'components/View';
11+import { useRollerStore } from 'store/roller';
12−import MaybeSigil from 'components/MaybeSigil';
13−import { matchBlinky } from 'components/Blinky';
14
12
15import { useLocalRouter } from 'lib/LocalRouter';
13import { useLocalRouter } from 'lib/LocalRouter';
16−import useInvites from 'lib/useInvites';
17import * as need from 'lib/need';
14import * as need from 'lib/need';
18import * as wg from 'lib/walletgen';
15import * as wg from 'lib/walletgen';
16+import useRoller from 'lib/useRoller';
17+import { convertToInt } from 'lib/convertToInt';
18+import { generateUrl } from 'lib/utils/invite';
19
19
20−import { ReactComponent as SearchIcon } from 'assets/search.svg';
20+import { useIssueChild } from 'views/IssueChild';
21−import CopyButton from 'components/copiable/CopyButton';
21+import View from 'components/View';
22−import { useNetwork } from 'store/network';
23import L2BackHeader from 'components/L2/Headers/L2BackHeader';
22import L2BackHeader from 'components/L2/Headers/L2BackHeader';
24import Window from 'components/L2/Window/Window';
23import Window from 'components/L2/Window/Window';
25import HeaderPane from 'components/L2/Window/HeaderPane';
24import HeaderPane from 'components/L2/Window/HeaderPane';
26−import { Icon, Row, StatelessTextInput } from '@tlon/indigo-react';
27import BodyPane from 'components/L2/Window/BodyPane';
25import BodyPane from 'components/L2/Window/BodyPane';
28−import { useRollerStore } from 'store/roller';
29−import useRoller from 'lib/useRoller';
30−import FeeDropdown from 'components/L2/Dropdowns/FeeDropdown';
31import LoadingOverlay from 'components/L2/LoadingOverlay';
26import LoadingOverlay from 'components/L2/LoadingOverlay';
32−import { generateUrl } from 'lib/utils/invite';
33import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
27import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
34import Paginator from 'components/L2/Paginator';
28import Paginator from 'components/L2/Paginator';
29+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
35
30
36import './Cohort.scss';
31import './Cohort.scss';
37−import useInviter from 'lib/useInviter';
32+import { getStoredInvites, setStoredInvites } from 'store/storage/roller';
38
33
39const INVITES_PER_PAGE = 7;
34const INVITES_PER_PAGE = 7;
40const DEFAULT_NUM_INVITES = 5;
35const DEFAULT_NUM_INVITES = 5;
41
36
42−function SearchInput({ className, value, onChange }) {
37+export default function InviteCohort() {
43− return (
38+ const { pop, push, names } = useLocalRouter();
44− <Flex align="center" full className={cn('b b-gray2 b1', className)}>
39+ const { authToken } = useWallet();
45− <Flex.Item className="p2">
40+ const {
46− <SearchIcon />
41+ nextRoll,
47− </Flex.Item>
42+ currentL2,
48−
43+ pendingTransactions,
49− <Flex.Item
44+ invites,
50− flex={1}
45+ } = useRollerStore();
51− as={'input'}
46+ const { generateInviteCodes, getPendingTransactions } = useRoller();
52− value={value}
47+ const { pointCursor } = usePointCursor();
53− onChange={onChange}
48+ const { contracts } = useNetwork();
54− className="b-none h7"
49+ const { getDetails } = usePointCache();
55− />
56− </Flex>
57− );
58−}
59
50
60−function CohortMember({ point, pending = false, className, ...rest }) {
51+ const point = need.point(pointCursor);
61− const { sentInvites } = useInvites(point);
52+ const _contracts = need.contracts(contracts);
62− const patp = useMemo(() => ob.patp(point), [point]);
63− const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
64
53
65− const DetailText = useCallback(
54+ const pointSize = azimuth.azimuth.getPointSize(point);
66− () =>
55+ const isParent = pointSize !== azimuth.azimuth.PointSize.Planet;
67− pending ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
56+ const details = need.details(getDetails(point));
68− [pending, sentInvites]
57+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
69− );
58+ const networkKeysNotSet = !currentL2 && isParent && networkRevision === 0;
70
59
71− return (
60+ const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
72− <Flex justify="between" className={cn('b1 b-gray2', className)} {...rest}>
73− <Flex.Item className="w9 h9">
74− <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
75− </Flex.Item>
76
61
77− <Flex.Item
62+ const [numInvites, setNumInvites] = useState(
78− flex={1}
63+ currentL2 ? DEFAULT_NUM_INVITES : 1
79− justify="evenly"
80− as={Flex}
81− col
82− className="mono f6 ph4">
83− <Flex.Item>{patp}</Flex.Item>
84− <Flex.Item className="gray4">
85− <DetailText />
86− </Flex.Item>
87− </Flex.Item>
88− </Flex>
89 );
64 );
90−}
65+ const [showInviteForm, setShowInviteForm] = useState(false);
91−function CohortList({
66+ const [loading, setLoading] = useState(false);
92− acceptedPoints,
67+ const [error, setError] = useState(null);
93− pendingPoints,
68+ const [l1Invite, setL1Invite] = useState(null);
94− className,
95− onlyPending,
96− onSelectInvite,
97−}) {
98− const { syncInvites } = usePointCache();
99
69
100− const [query, setQuery] = useState('');
70+ const { construct, unconstruct, completed, bind } = useIssueChild();
101
71
72+ // Set up the invite spawn if on L1
102 useEffect(() => {
73 useEffect(() => {
103− acceptedPoints.map(syncInvites);
74+ const _authToken = authToken.getOrElse(null);
104− }, [acceptedPoints, syncInvites]);
105−
106− const handleChange = useCallback(
107− e => {
108− setQuery(e.target.value);
109− e.preventDefault();
110− },
111− [setQuery]
112− );
113−
114− const filterPoints = useCallback(
115− points =>
116− points.filter(p => {
117− const patp = ob.patp(p).slice(1);
118− return patp.startsWith(query);
119− }),
120− [query]
121− );
122− const selectInvite = useCallback(
123− point => () => {
124− onSelectInvite(point);
125− },
126− [onSelectInvite]
127− );
128− const _pendingPoints = filterPoints(pendingPoints);
129− const _acceptedPoints = filterPoints(acceptedPoints);
130−
131− const offset = onlyPending ? 0 : _acceptedPoints.length;
132−
133− return (
134− <Grid gap={3} className={cn('mt4', className)}>
135− <Grid.Item full as={SearchInput} value={query} onChange={handleChange} />
136− <>
137− {!onlyPending &&
138− _acceptedPoints.map((p, idx) => (
139− <Grid.Item
140− key={p}
141− half={(idx % 2) + 1}
142− as={CohortMember}
143− point={p}
144− onClick={selectInvite(p)}
145− />
146− ))}
147− {_pendingPoints.map((p, idx) => (
148− <Grid.Item
149− key={p}
150− half={((offset + idx) % 2) + 1}
151− as={CohortMember}
152− point={p}
153− pending
154− onClick={selectInvite(p)}
155− />
156− ))}
157− </>
158−
159− {_pendingPoints.length === 0 &&
160− (_acceptedPoints.length === 0 || onlyPending) && (
161− <Grid.Item full className="p4 t-center">
162− {' '}
163− No invites accepted yet.
164− </Grid.Item>
165− )}
166− </Grid>
167− );
168−}
169−
170−function hideIf(hider, hidden) {
171− return hider ? '●●●●●●●' : hidden;
172−}
173
75
174−function CohortMemberExpanded({ point, className, ...rest }) {
76+ const setUpInvite = async () => {
175− const { sentInvites, acceptedInvites } = useInvites(point);
77+ setLoading(true);
176− const { getDetails } = usePointCache();
78+ const [invitePoint] = await azimuth.azimuth.getUnspawnedChildren(
177− const { authToken } = useWallet();
79+ _contracts,
178− const { contracts } = useNetwork();
80+ point
179− const details = getDetails(point);
81+ );
180− const { active } = details.getOrElse({});
181− const patp = useMemo(() => ob.patp(point), [point]);
182− const colors = useMemo(
183− () => (!active ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF']),
184− [active]
185− );
186−
187− const [code, setCode] = useState(Nothing());
188− const [codeVisible, setCodeVisible] = useState(false);
189
82
190− useEffect(() => {
83+ // TODO: figure out how to store the ticket and invite info
191− const fetchWallet = async () => {
192− const _authToken = authToken.getOrElse(null);
193− const _details = details.getOrElse(null);
194− const _contracts = contracts.getOrElse(null);
195− if (
196− !_authToken ||
197− !_details ||
198− _details.active ||
199− !_contracts ||
200− !codeVisible
201− ) {
202− setCode(Nothing());
203− return;
204− }
205 const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
84 const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
206 point,
85 point,
207 _authToken
86 _authToken
208 );
87 );
209
88
210− if (owner.keys.address !== _details.transferProxy) {
89+ construct(invitePoint, owner.keys.address);
211− setCode(Just(null));
90+ setL1Invite({ ticket, planet: invitePoint });
212− } else {
91+ setLoading(false);
213− setCode(Just(ticket));
214− }
215 };
92 };
216− fetchWallet();
217− }, [authToken, codeVisible, contracts, details, point]);
218−
219− useEffect(() => {
220− setCodeVisible(false);
221− }, [point]);
222−
223− return (
224− <Grid justify="between" className={cn('b1 b-gray2', className)} {...rest}>
225− <Grid.Item rows={[1, 4]} cols={[1, 4]} className="h10 w10">
226− <MaybeSigil patp={Just(patp)} size={64} colors={colors} />
227− </Grid.Item>
228−
229− <Grid.Item cols={[4, 13]} className="mono mt-auto ">
230− {patp}
231− </Grid.Item>
232−
233− {!active && (
234− <>
235− <Grid.Item
236− cols={[4, 13]}
237− className="mt-auto mb1 f6 gray4"
238− as={Flex}
239− col>
240− Invite Code
241− </Grid.Item>
242− <Grid.Item cols={[4, 10]} className="mb-auto mt1 f6 gray4">
243− {(code.getOrElse(true) === null &&
244− 'This invite code cannot be recovered') ||
245− hideIf(!codeVisible, matchBlinky(code))}
246− </Grid.Item>
247− {!codeVisible && (
248− <Grid.Item
249− cols={[10, 13]}
250− className="mh-auto f6 underline pointer"
251− onClick={() => setCodeVisible(true)}>
252− Show
253− </Grid.Item>
254− )}
255− {codeVisible && code.getOrElse('') !== null && (
256− <Grid.Item
257− className="mh-auto f6 t-right"
258− cols={[10, 13]}
259− as={CopyButton}
260− text={code.getOrElse('')}
261− />
262− )}
263− </>
264− )}
265−
266− {active && (
267− <>
268− <Grid.Item
269− cols={[4, 13]}
270− className="mt-auto mb1 f6 gray4"
271− as={Flex}
272− col>
273− {matchBlinky(sentInvites)} invites sent
274− </Grid.Item>
275− <Grid.Item
276− cols={[4, 13]}
277− className="mb-auto mt1 f6 gray4"
278− as={Flex}
279− col>
280− {matchBlinky(acceptedInvites)} invites accepted
281− </Grid.Item>
282− </>
283− )}
284− </Grid>
285− );
286−}
287
93
288−export default function InviteCohort() {
94+ if (!currentL2 && _contracts && point && _authToken && showInviteForm) {
289− const { pop } = useLocalRouter();
95+ setUpInvite();
290− const {
96+ } else {
291− nextRoll,
97+ unconstruct();
98+ }
99+ }, [
292 currentL2,
100 currentL2,
293− pendingTransactions,
101+ point,
294− invites,
102+ _contracts,
295− } = useRollerStore();
103+ authToken,
296− const { generateInvites } = useInviter();
104+ construct,
297− const { generateInviteCodes, getPendingTransactions } = useRoller();
105+ showInviteForm,
298−
106+ unconstruct,
299− const { pointCursor } = usePointCursor();
107+ ]);
300− const point = need.point(pointCursor);
301
108
302− const [page, setPage] = useState(0);
109+ const l1Complete = useCallback(() => {
110+ const { available, pending, claimed } = getStoredInvites(point);
111+ setStoredInvites(point, {
112+ available: [...available, l1Invite],
113+ pending,
114+ claimed,
115+ });
116+ setShowInviteForm(false);
117+ }, [l1Invite, point]);
303
118
304− const { pendingPoints } = useInvites(point);
119+ useEffect(() => (completed ? l1Complete() : null), [completed, l1Complete]);
305
120
306− const displayedInvites = currentL2 ? invites : pendingPoints.getOrElse([]);
121+ const [page, setPage] = useState(0);
307
122
308− const invitesToDisplay = displayedInvites.slice(
123+ const invitesToDisplay = invites.slice(
309 page * INVITES_PER_PAGE,
124 page * INVITES_PER_PAGE,
310 (page + 1) * INVITES_PER_PAGE
125 (page + 1) * INVITES_PER_PAGE
311 );
126 );
312
127
313 const hasPending = Boolean(pendingTransactions.length);
128 const hasPending = Boolean(pendingTransactions.length);
314− const hasInvites = Boolean(displayedInvites.length);
129+ const hasInvites = Boolean(invites.length);
315−
316− const [numInvites, setNumInvites] = useState(DEFAULT_NUM_INVITES);
317− const [showInviteForm, setShowInviteForm] = useState(false);
318− const [loading, setLoading] = useState(false);
319− const [error, setError] = useState(null);
320
130
321 const createInvites = useCallback(async () => {
131 const createInvites = useCallback(async () => {
322 setLoading(true);
132 setLoading(true);
323 try {
133 try {
324− if (currentL2) {
325− const invites = await generateInviteCodes(numInvites);
134+ const invites = await generateInviteCodes(numInvites);
326− console.log('INVITES', invites);
135+ console.log('L2 INVITES', invites);
327− getPendingTransactions(point);
136+ getPendingTransactions(point);
328− } else {
137+ pop();
329− const invites = await generateInvites(numInvites);
330− console.log('INVITES', invites);
331− }
332− setShowInviteForm(false);
333 } catch (error) {
138 } catch (error) {
334 setError(error);
139 setError(error);
335 } finally {
140 } finally {
336 setLoading(false);
141 setLoading(false);
337 }
142 }
338− }, [
339− generateInviteCodes,
340− numInvites,
341− getPendingTransactions,
143+ }, [generateInviteCodes, numInvites, getPendingTransactions, point, pop]);
342− point,
343− generateInvites,
344− currentL2,
345− ]);
346
144
347 const getContent = () => {
145 const getContent = () => {
348 if (hasInvites) {
146 if (hasInvites) {
@@ -378,6 +176,18 @@ export default function InviteCohort() {
378 Check back in <span className="timer"> {nextRoll}</span>
176 Check back in <span className="timer"> {nextRoll}</span>
379 </>
177 </>
380 );
178 );
179+ } else if (networkKeysNotSet) {
180+ return (
181+ <>
182+ This star's network keys are not set.
183+ <br />
184+ Either set the network keys or{' '}
185+ <span className="migrate" onClick={() => null}>
186+ {/* TODO: change this to navigate to migration */}
187+ migrate this star to L2.
188+ </span>
189+ </>
190+ );
381 }
191 }
382
192
383 return (
193 return (
@@ -398,8 +208,8 @@ export default function InviteCohort() {
398 const header = (
208 const header = (
399 <h5>
209 <h5>
400 You have
210 You have
401− <span className="number-emphasis"> {displayedInvites.length} </span>
211+ <span className="number-emphasis"> {invites.length} </span>
402− Planet Code{displayedInvites.length === 1 ? '' : 's'}
212+ Planet Code{invites.length === 1 ? '' : 's'}
403 </h5>
213 </h5>
404 );
214 );
405
215
@@ -415,18 +225,29 @@ export default function InviteCohort() {
415 </HeaderPane>
225 </HeaderPane>
416 <BodyPane>
226 <BodyPane>
417 <div className="upper">
227 <div className="upper">
228+ {currentL2 ? (
418− <Row className="points-input">
229+ <Row className="points-input">
419− I want to generate
230+ I want to generate
420− <StatelessTextInput
231+ <StatelessTextInput
421− className="input-box"
232+ className="input-box"
422− value={numInvites}
233+ value={numInvites}
423− maxLength="3"
234+ maxLength="3"
424− onChange={e =>
235+ onChange={e =>
425− setNumInvites(Number(e.target.value.replace(/\D/g, '')))
236+ setNumInvites(Number(e.target.value.replace(/\D/g, '')))
426− }
237+ }
427− />
238+ />
428− planet invite code{numInvites > 1 ? 's' : ''}
239+ planet invite code{numInvites > 1 ? 's' : ''}
429− </Row>
240+ </Row>
241+ ) : (
242+ <div className="migration-prompt">
243+ ETH fees are very high currently. You can spawn invites for
244+ free after{' '}
245+ <span className="migrate" onClick={() => null}>
246+ {/* TODO: change this to navigate to migration */}
247+ migrating this star to L2.
248+ </span>
249+ </div>
250+ )}
430 {/* <div>Destination</div>
251 {/* <div>Destination</div>
431 <div className="ship-or-address">Ship or ethereum address</div>
252 <div className="ship-or-address">Ship or ethereum address</div>
432 <Dropdown value={dropdownValue}>
253 <Dropdown value={dropdownValue}>
@@ -439,22 +260,30 @@ export default function InviteCohort() {
439 </div>
260 </div>
440 {/* <Inviter /> */}
261 {/* <Inviter /> */}
441 <div className="lower">
262 <div className="lower">
263+ {currentL2 && (
442− <Row className="next-roll">
264+ <Row className="next-roll">
443− <span>{currentL2 ? 'Next Roll in' : 'Transaction Fee'}</span>
265+ <span>Next Roll in</span>
444− {currentL2 ? (
445 <span className="timer">{nextRoll}</span>
266 <span className="timer">{nextRoll}</span>
446− ) : (
447− <FeeDropdown />
448− )}
449− </Row>
267+ </Row>
268+ )}
269+ {currentL2 ? (
450− <Button
270+ <Button
451− as={'button'}
271+ as={'button'}
452− className="generate-codes"
272+ className="generate-codes"
453− center
273+ center
454− solid
274+ solid
455− onClick={createInvites}>
275+ onClick={createInvites}>
456− Generate Planet Codes ({numInvites})
276+ Generate Planet Code{currentL2 ? `s (${numInvites})` : ''}
457− </Button>
277+ </Button>
278+ ) : (
279+ <Grid.Item
280+ full
281+ as={InlineEthereumTransaction}
282+ label="Generate Planet Code"
283+ {...bind}
284+ onReturn={() => pop()}
285+ />
286+ )}
458 </div>
287 </div>
459 </BodyPane>
288 </BodyPane>
460 </Window>
289 </Window>
@@ -463,8 +292,15 @@ export default function InviteCohort() {
463 );
292 );
464 }
293 }
465
294
295+ const showGenerateButton = !hasPending && !hasInvites && !networkKeysNotSet;
296+
466 return (
297 return (
298+ <View
299+ pop={pop}
300+ inset
467− <View pop={pop} inset className="cohort" hideBack header={<L2BackHeader />}>
301+ className="cohort"
302+ hideBack
303+ header={<L2BackHeader hideBalance />}>
468 <Window>
304 <Window>
469 <HeaderPane>
305 <HeaderPane>
470 {!hasInvites ? (
306 {!hasInvites ? (
@@ -483,7 +319,7 @@ export default function InviteCohort() {
483 <div className={`content ${!hasInvites ? 'center' : ''}`}>
319 <div className={`content ${!hasInvites ? 'center' : ''}`}>
484 {getContent()}
320 {getContent()}
485 </div>
321 </div>
486− {!hasPending && !hasInvites && (
322+ {showGenerateButton && (
487 <Button
323 <Button
488 as={'button'}
324 as={'button'}
489 className="generate-button"
325 className="generate-button"
@@ -493,11 +329,21 @@ export default function InviteCohort() {
493 Generate Codes
329 Generate Codes
494 </Button>
330 </Button>
495 )}
331 )}
332+ {networkKeysNotSet && (
333+ <Button
334+ as={'button'}
335+ className="generate-button"
336+ center
337+ solid
338+ onClick={goUrbitOS}>
339+ Set Network Keys
340+ </Button>
341+ )}
496 {hasInvites && (
342 {hasInvites && (
497 <Paginator
343 <Paginator
498 page={page}
344 page={page}
499 numPerPage={INVITES_PER_PAGE}
345 numPerPage={INVITES_PER_PAGE}
500− numElements={displayedInvites.length}
346+ numElements={invites.length}
501 goPrevious={() => setPage(page - 1)}
347 goPrevious={() => setPage(page - 1)}
502 goNext={() => setPage(page + 1)}
348 goNext={() => setPage(page + 1)}
503 />
349 />
src/views/Invite/Cohort.scss+11−1
@@ -109,12 +109,22 @@
109 }
109 }
110 }
110 }
111
111
112+ .migrate {
113+ color: $mid-gray;
114+ text-decoration: underline;
115+ cursor: pointer;
116+ }
117+
112 &.show-invite-form {
118 &.show-invite-form {
113 .body-pane {
119 .body-pane {
114 .upper, .lower {
120 .upper, .lower {
115 width: 100%;
121 width: 100%;
116 }
122 }
117−
123+
124+ .migration-prompt {
125+ line-height: 20px;
126+ }
127+
118 .points-input {
128 .points-input {
119 line-height: 28px;
129 line-height: 28px;
120 margin-bottom: 24px;
130 margin-bottom: 24px;
src/views/IssueChild.js+2−1
@@ -32,7 +32,7 @@ import FormError from 'form/FormError';
32import CopiableAddress from 'components/copiable/CopiableAddress';
32import CopiableAddress from 'components/copiable/CopiableAddress';
33import { convertToInt } from 'lib/convertToInt';
33import { convertToInt } from 'lib/convertToInt';
34
34
35−function useIssueChild() {
35+export function useIssueChild() {
36 const { contracts } = useNetwork();
36 const { contracts } = useNetwork();
37 const { syncDates } = usePointCache();
37 const { syncDates } = usePointCache();
38
38
@@ -43,6 +43,7 @@ function useIssueChild() {
43 return useEthereumTransaction(
43 return useEthereumTransaction(
44 useCallback(
44 useCallback(
45 (spawnedPoint, owner) => {
45 (spawnedPoint, owner) => {
46+ console.log(1, spawnedPoint, owner, _contracts)
46 setSpawnedPoint(spawnedPoint);
47 setSpawnedPoint(spawnedPoint);
47 return azimuth.ecliptic.spawn(_contracts, spawnedPoint, owner);
48 return azimuth.ecliptic.spawn(_contracts, spawnedPoint, owner);
48 },
49 },
src/views/Point.js+18−46
@@ -2,37 +2,33 @@ import React, { useCallback, useEffect, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Just } from 'folktale/maybe';
3import { Just } from 'folktale/maybe';
4import { Grid, Flex, Button } from 'indigo-react';
4import { Grid, Flex, Button } from 'indigo-react';
5+import { Icon, Row } from '@tlon/indigo-react';
5import { azimuth } from 'azimuth-js';
6import { azimuth } from 'azimuth-js';
6−import * as ob from 'urbit-ob';
7−import * as wg from 'lib/walletgen';
8
7
9import { usePointCursor } from 'store/pointCursor';
8import { usePointCursor } from 'store/pointCursor';
10import { useWallet } from 'store/wallet';
9import { useWallet } from 'store/wallet';
10+import { usePointCache } from 'store/pointCache';
11+import { useRollerStore } from 'store/roller';
11
12
12import View from 'components/View';
13import View from 'components/View';
13import Greeting from 'components/Greeting';
14import Greeting from 'components/Greeting';
14import Passport from 'components/Passport';
15import Passport from 'components/Passport';
15−import Blinky, { matchBlinky } from 'components/Blinky';
16+import Blinky from 'components/Blinky';
16import BarGraph from 'components/BarGraph';
17import BarGraph from 'components/BarGraph';
17import Chip from 'components/Chip';
18import Chip from 'components/Chip';
18import InviteSigilList from 'components/InviteSigilList';
19import InviteSigilList from 'components/InviteSigilList';
19import { ForwardButton } from 'components/Buttons';
20import { ForwardButton } from 'components/Buttons';
21+import L2PointHeader from 'components/L2/Headers/L2PointHeader';
22+import LayerIndicator from 'components/L2/LayerIndicator';
23+import Card from 'components/L2/Card';
20
24
21import * as need from 'lib/need';
25import * as need from 'lib/need';
22import useInvites from 'lib/useInvites';
26import useInvites from 'lib/useInvites';
23import { useSyncExtras } from 'lib/useSyncPoints';
27import { useSyncExtras } from 'lib/useSyncPoints';
24import useCurrentPermissions from 'lib/useCurrentPermissions';
28import useCurrentPermissions from 'lib/useCurrentPermissions';
25import { useLocalRouter } from 'lib/LocalRouter';
29import { useLocalRouter } from 'lib/LocalRouter';
26−
27import Inviter from 'views/Invite/Inviter';
30import Inviter from 'views/Invite/Inviter';
28−import { usePointCache } from 'store/pointCache';
29−import Card from 'components/L2/Card';
30−import { Icon, Row } from '@tlon/indigo-react';
31−import L2PointHeader from 'components/L2/Headers/L2PointHeader';
32import useRoller from 'lib/useRoller';
31import useRoller from 'lib/useRoller';
33−import { useRollerStore } from 'store/roller';
34−import LayerIndicator from 'components/L2/LayerIndicator';
35−import { convertToInt } from 'lib/convertToInt';
36
32
37import './Point.scss';
33import './Point.scss';
38
34
@@ -132,30 +128,31 @@ export default function Point() {
132 const {
128 const {
133 pendingTransactions,
129 pendingTransactions,
134 nextRoll,
130 nextRoll,
135− setCurrentPoint,
136− currentL2,
137 invites,
131 invites,
138 recentlyCompleted,
132 recentlyCompleted,
133+ setCurrentPoint,
134+ setPendingTransactions,
139 } = useRollerStore();
135 } = useRollerStore();
140
136
137+ const [loaded, setLoaded] = useState(false);
138+
141 useEffect(() => {
139 useEffect(() => {
142 const loadL2Info = async () => {
140 const loadL2Info = async () => {
143− console.log('POINT', point);
144 const getTransactions = async () => {
141 const getTransactions = async () => {
145 const pointInfo = await api.getPoint(Number(point));
142 const pointInfo = await api.getPoint(Number(point));
146 setCurrentPoint(pointInfo);
143 setCurrentPoint(pointInfo);
147− console.log('POINT', pointInfo);
148
144
149− if (pointInfo.dominion !== 'l1') {
150− getInvites();
145+ getInvites();
151− }
152 };
146 };
153
147
148+ if (!loaded) {
154− getTransactions();
149+ getTransactions();
150+ setLoaded(true);
151+ }
155 };
152 };
156
153
157 loadL2Info();
154 loadL2Info();
158− }, [api, point, authToken]); // eslint-disable-line
155+ }, [api, point, authToken, setPendingTransactions, loaded]); // eslint-disable-line
159
156
160 const { residentCount, requestCount } = getResidents(point);
157 const { residentCount, requestCount } = getResidents(point);
161
158
@@ -197,11 +194,6 @@ export default function Point() {
197
194
198 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
195 const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
199
196
200− const goPartiesSetPoolSize = useCallback(
201− () => push(names.PARTY_SET_POOL_SIZE),
202− [push, names]
203− );
204−
205 const goIssuePoint = useCallback(() => push(names.ISSUE_CHILD), [
197 const goIssuePoint = useCallback(() => push(names.ISSUE_CHILD), [
206 names.ISSUE_CHILD,
198 names.ISSUE_CHILD,
207 push,
199 push,
@@ -211,25 +203,6 @@ export default function Point() {
211
203
212 const [showInviteForm, setShowInviteForm] = useState(false);
204 const [showInviteForm, setShowInviteForm] = useState(false);
213
205
214− const inviteButton = (() => {
215− if (azimuth.getPointSize(point) === azimuth.PointSize.Star) {
216− return (
217− <>
218− <Grid.Item
219− full
220− as={ForwardButton}
221− disabled={!isActiveOwner}
222− onClick={goPartiesSetPoolSize}>
223− Manage Invite Pools
224− </Grid.Item>
225− <Grid.Divider />
226− </>
227− );
228− }
229−
230− return null;
231− })();
232−
233 const senateButton = (() => {
206 const senateButton = (() => {
234 if (azimuth.getPointSize(point) !== azimuth.PointSize.Galaxy) {
207 if (azimuth.getPointSize(point) !== azimuth.PointSize.Galaxy) {
235 return null;
208 return null;
@@ -255,7 +228,6 @@ export default function Point() {
255
228
256 const _requestCount = requestCount.getOrElse(0);
229 const _requestCount = requestCount.getOrElse(0);
257 const numPending = pendingTransactions.length;
230 const numPending = pendingTransactions.length;
258− const numInvites = currentL2 ? invites.length : sentInvites.length;
259
231
260 return (
232 return (
261 <View
233 <View
@@ -263,7 +235,7 @@ export default function Point() {
263 inset
235 inset
264 className="point"
236 className="point"
265 header={
237 header={
266− <L2PointHeader hideTimer={!!numPending} numInvites={numInvites} />
238+ <L2PointHeader hideTimer={!!numPending} numInvites={invites.length} />
267 }>
239 }>
268 <Greeting point={point} />
240 <Greeting point={point} />
269 {!!numPending && (
241 {!!numPending && (
src/views/Points.js+2−1
@@ -37,7 +37,8 @@ export const maybeGetResult = (obj, key, defaultValue) =>
37 }),
37 }),
38 });
38 });
39
39
40+export const hasTransferProxy = details =>
40−const hasTransferProxy = details => !isZeroAddress(details.transferProxy);
41+ !isZeroAddress(details.transferProxy);
41
42
42const PointList = function({
43const PointList = function({
43 points,
44 points,
src/views/UrbitID/Home.js+3
@@ -30,6 +30,8 @@ export default function UrbitIDHome() {
30 names,
30 names,
31 ]);
31 ]);
32
32
33+ const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
34+
33 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
35 const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
34
36
35 const isMasterTicket = Just.hasInstance(urbitWallet);
37 const isMasterTicket = Just.hasInstance(urbitWallet);
@@ -135,6 +137,7 @@ export default function UrbitIDHome() {
135 <Grid.Item
137 <Grid.Item
136 full
138 full
137 as={ForwardButton}
139 as={ForwardButton}
140+ onClick={goUrbitOS}
138 className="f6"
141 className="f6"
139 accessory={<span className="underline pointer">Set Keys</span>}>
142 accessory={<span className="underline pointer">Set Keys</span>}>
140 Network Keys Required
143 Network Keys Required