on master help sign in
Lots of spit and polish
commit938c09813957dffb699a785ced4c05e54f51c58a authorWill Galebach <willbach@gmail.com> dateSun 12 Sep 2021, 21:20 (5 years ago) parentac1828f
17 files changed+340−209
package-lock.json+6−6 package.json+1−1 src/components/L2/Dropdowns/AccountsDropdown.scss+63 src/components/L2/Dropdowns/AccountsDropdown.tsx+85−23 src/components/L2/Headers/L2BackHeader.tsx+3−1 src/components/L2/Headers/L2PointHeader.scss−59 src/components/L2/Headers/L2PointHeader.tsx+7−39 src/components/L2/LoadingOverlay.scss+2−1 src/lib/useRoller.ts+3−3 src/lib/utils/roller.ts+3−2 src/store/lib/useControlledPointsStore.js+14−18 src/views/Invite/Cohort.js+14−38 src/views/Invite/Cohort.scss+4 src/views/Point/MigrateL2.tsx+12−4 src/views/Point/Point.js+55−11 src/views/Point/Point.scss+67−2 src/views/Points.js+1−1
package-lock.json+6−6
@@ -7755,9 +7755,9 @@
77557755 }
77567756 },
77577757 "@urbit/roller-api": {
7758− "version": "0.1.6",
7759− "resolved": "https://registry.npmjs.org/@urbit/roller-api/-/roller-api-0.1.6.tgz",
7760− "integrity": "sha512-JgZTugNzIPzL9wgvPSTaUu8ts1hi4Rgka4QwxBMGq6qeJo5S3q47cNg+dV+TVYcIYOyoNMAOq+1K9jEyui4ayQ==",
7758+ "version": "0.1.7",
7759+ "resolved": "https://registry.npmjs.org/@urbit/roller-api/-/roller-api-0.1.7.tgz",
7760+ "integrity": "sha512-XkqiYUBZwWM0xfwONxFyATqylZbx/cFH+ToHssw1lGD9q2UiZOISDBVHULgimQy6zCnxTwvlAvDZ6Eozl0L9RQ==",
77617761 "requires": {
77627762 "@open-rpc/client-js": "1.7.0",
77637763 "@open-rpc/meta-schema": "1.14.2",
@@ -14642,9 +14642,9 @@
1464214642 "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
1464314643 },
1464414644 "fast-safe-stringify": {
14645− "version": "2.0.8",
14646− "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz",
14647− "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag=="
14645+ "version": "2.1.1",
14646+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
14647+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
1464814648 },
1464914649 "fast-sha256": {
1465014650 "version": "1.3.0",
package.json+1−1
@@ -32,7 +32,7 @@
3232 "@types/styled-components": "^5.1.10",
3333 "@types/styled-system": "^5.1.11",
3434 "@types/styled-system__css": "^5.0.15",
35− "@urbit/roller-api": "^0.1.6",
35+ "@urbit/roller-api": "^0.1.7",
3636 "@walletconnect/client": "^1.6.2",
3737 "@walletconnect/qrcode-modal": "^1.6.2",
3838 "@welldone-software/why-did-you-render": "^3.2.3",
src/components/L2/Dropdowns/AccountsDropdown.scss+63
@@ -4,6 +4,10 @@
44 font-size: 14px;
55 position: relative;
66
7+ .selector {
8+ font-family: "Source Code Pro", "Roboto mono", "Courier New", monospace;
9+ }
10+
711 .content-border {
812 .dropdown-content {
913 .entry, .current-address {
@@ -47,3 +51,62 @@
4751 }
4852 }
4953}
54+
55+.migrate-modal {
56+ padding: 16px;
57+ background-color: white;
58+ border-radius: $medium-border-radius;
59+ line-height: 24px;
60+ position: relative;
61+ width: 368px;
62+
63+ .close {
64+ position: absolute;
65+ top: 3px;
66+ right: 8px;
67+ font-size: 18px;
68+ font-weight: 200;
69+ cursor: pointer;
70+ }
71+
72+ .title {
73+ font-weight: 700;
74+ }
75+
76+ .message {
77+ width: 340px;
78+ }
79+
80+ .warning {
81+ font-weight: 600;
82+ color: $warning-red;
83+ }
84+
85+ .buttons {
86+ display: flex;
87+ flex-direction: row;
88+ justify-content: flex-end;
89+ margin-top: 24px;
90+
91+ .cancel, .migrate {
92+ border-radius: $medium-border-radius;
93+ border: none;
94+ height: 32px;
95+ font-family: $default-font;
96+
97+ &:hover {
98+ opacity: 0.8;
99+ }
100+ }
101+
102+ .cancel {
103+ margin-right: 8px;
104+ background-color: $washed-gray;
105+ }
106+
107+ .migrate {
108+ background-color: black;
109+ color: white;
110+ }
111+ }
112+}
src/components/L2/Dropdowns/AccountsDropdown.tsx+85−23
@@ -1,43 +1,82 @@
1−import React, { useState } from 'react';
1+import React, { useCallback, useState } from 'react';
22import * as ob from 'urbit-ob';
3−import { Icon, Row, Box } from '@tlon/indigo-react';
3+import { azimuth } from 'azimuth-js';
4+import * as need from 'lib/need';
5+import { Icon, Row, Box, Button } from '@tlon/indigo-react';
46import { Just } from 'folktale/maybe';
57
68import Sigil from 'components/Sigil';
79import LayerIndicator from 'components/L2/LayerIndicator';
10+import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
11+
812import { useWallet } from 'store/wallet';
913import { useHistory } from 'store/history';
1014import { usePointCursor } from 'store/pointCursor';
1115import { usePointCache } from 'store/pointCache';
1216import { clearInvitesStorage } from 'store/storage/roller';
13−import CopiableAddressWrap from 'components/copiable/CopiableAddressWrap';
14−import Dropdown from './Dropdown';
17+import { useRollerStore } from 'store/roller';
1518
16−import './AccountsDropdown.scss';
17−import { abbreviateAddress } from 'lib/utils/address';
1819import { PointLayer } from 'lib/types/PointLayer';
20+import { abbreviateAddress } from 'lib/utils/address';
21+
22+import Dropdown from './Dropdown';
23+import './AccountsDropdown.scss';
24+import Modal from '../Modal';
25+import { convertToInt } from 'lib/convertToInt';
1926
20−const AccountsDropdown = () => {
27+interface AccountsDropdownProps {
28+ showMigrate: boolean;
29+}
30+
31+const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
2132 const [open, setOpen] = useState<boolean>(false);
2233 const walletInfo: any = useWallet();
2334 const { push, names, reset }: any = useHistory();
2435 const { setPointCursor }: any = usePointCursor();
2536 const { controlledPoints }: any = usePointCache();
37+ const { currentL2 } = useRollerStore();
38+ const { getDetails }: any = usePointCache();
39+ const { pointCursor }: any = usePointCursor();
40+ const point = pointCursor.getOrElse(null);
41+
42+ let networkKeysNotSet = true;
43+
44+ if (point) {
45+ const pointSize = azimuth.getPointSize(point);
46+ const details = need.details(getDetails(point));
47+ const isStarOrGalaxy = pointSize !== azimuth.PointSize.Planet;
48+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
49+ networkKeysNotSet = !currentL2 && isStarOrGalaxy && networkRevision === 0;
50+ }
51+
52+ const [showModal, setShowModal] = useState(false);
2653
2754 const canBitcoin = Just.hasInstance(walletInfo.urbitWallet);
2855
29− const points = controlledPoints?.value?.value?.pointsWithLayers || [];
56+ const points = (controlledPoints?.value?.value?.pointsWithLayers || []).sort(
57+ (a: PointLayer, b: PointLayer) => a.point - b.point
58+ );
3059
3160 const currentAddress = walletInfo?.wallet?.value?.address || '';
3261 const displayAddress = abbreviateAddress(currentAddress);
3362
34− const selectPoint = (point: number) => () => {
35− setPointCursor(Just(point));
36− push(names.POINT);
37− setOpen(false);
38− };
39− const goToMigrate = () => push(names.MIGRATE);
40− const goToBitcoin = () => push(names.BITCOIN);
63+ const selectPoint = useCallback(
64+ (point: number) => () => {
65+ setPointCursor(Just(point));
66+ push(names.POINT);
67+ setOpen(false);
68+ },
69+ [push, names.POINT, setOpen, setPointCursor]
70+ );
71+ const goMigrate = useCallback(() => push(names.MIGRATE_L2), [
72+ names.MIGRATE_L2,
73+ push,
74+ ]);
75+ const goBitcoin = useCallback(() => push(names.BITCOIN), [
76+ names.BITCOIN,
77+ push,
78+ ]);
79+
4180 const onLogout = () => {
4281 clearInvitesStorage();
4382 reset();
@@ -74,16 +113,18 @@ const AccountsDropdown = () => {
74113 })}
75114 </Box>
76115 <Box className="divider" />
77− <Row className="entry" onClick={goToMigrate}>
78− <Box>Migrate</Box>
79− <Row className="layer-migration">
80− <LayerIndicator layer={1} size="sm" />
81− <Icon icon="ArrowEast" />
82− <LayerIndicator layer={2} size="sm" />
116+ {(showMigrate || (!currentL2 && !networkKeysNotSet)) && (
117+ <Row className="entry" onClick={() => setShowModal(true)}>
118+ <Box>Migrate</Box>
119+ <Row className="layer-migration">
120+ <LayerIndicator layer={1} size="sm" />
121+ <Icon icon="ArrowEast" />
122+ <LayerIndicator layer={2} size="sm" />
123+ </Row>
83124 </Row>
84− </Row>
125+ )}
85126 {canBitcoin && (
86− <Row className="entry" onClick={goToBitcoin}>
127+ <Row className="entry" onClick={goBitcoin}>
87128 <Box>Bitcoin</Box>
88129 <Icon icon="Bitcoin" />
89130 </Row>
@@ -92,6 +133,27 @@ const AccountsDropdown = () => {
92133 <Box>Logout</Box>
93134 <Icon icon="LogOut" />
94135 </Row>
136+ <Modal show={showModal} hide={() => setShowModal(false)}>
137+ <Box className="migrate-modal">
138+ <Box className="close" onClick={() => setShowModal(false)}>
139+ &#215;
140+ </Box>
141+ <Box className="title">Migrating to Layer 2</Box>
142+ <Box className="message">
143+ We've upgraded Bridge to support Layer 2 transactions. If you don't
144+ migrate now, you can always do it later.
145+ </Box>
146+ <Box className="warning">Migrating to Layer 2 is irreversible.</Box>
147+ <Row className="buttons">
148+ <Button className="cancel" onClick={() => setShowModal(false)}>
149+ Cancel
150+ </Button>
151+ <Button className="migrate" onClick={goMigrate}>
152+ Migrate
153+ </Button>
154+ </Row>
155+ </Box>
156+ </Modal>
95157 </Dropdown>
96158 );
97159};
src/components/L2/Headers/L2BackHeader.tsx+3−1
@@ -47,7 +47,9 @@ const L2BackHeader = ({ back, hideBalance = false }: L2BackHeaderProps) => {
4747 const _wallet = wallet.getOrElse(null);
4848
4949 if (_web3 && _wallet) {
50− const newBalance = toBN(await _web3.eth.getBalance(_wallet.address));
50+ const newBalance = _web3.utils.fromWei(
51+ await _web3.eth.getBalance(_wallet.address)
52+ );
5153 setEthBalance(newBalance);
5254 }
5355 };
src/components/L2/Headers/L2PointHeader.scss−59
@@ -47,63 +47,4 @@
4747 width: 90px;
4848 }
4949 }
50−
51− .migrate-modal {
52− padding: 16px;
53− background-color: white;
54− border-radius: $medium-border-radius;
55− line-height: 24px;
56− position: relative;
57− width: 368px;
58−
59− .close {
60− position: absolute;
61− top: 3px;
62− right: 8px;
63− font-size: 18px;
64− font-weight: 200;
65− cursor: pointer;
66− }
67−
68− .title {
69− font-weight: 700;
70− }
71−
72− .message {
73− width: 340px;
74− }
75−
76− .warning {
77− font-weight: 600;
78− color: $warning-red;
79− }
80−
81− .buttons {
82− display: flex;
83− flex-direction: row;
84− justify-content: flex-end;
85− margin-top: 24px;
86−
87− .cancel, .migrate {
88− border-radius: $medium-border-radius;
89− border: none;
90− height: 32px;
91− font-family: $default-font;
92−
93− &:hover {
94− opacity: 0.8;
95− }
96− }
97−
98− .cancel {
99− margin-right: 8px;
100− background-color: $washed-gray;
101− }
102−
103− .migrate {
104− background-color: black;
105− color: white;
106− }
107− }
108− }
10950}
src/components/L2/Headers/L2PointHeader.tsx+7−39
@@ -1,13 +1,12 @@
1−import React, { useEffect, useCallback, useState } from 'react';
1+import React, { useEffect, useCallback } from 'react';
22import { azimuth } from 'azimuth-js';
3−import { Box, Button, Icon, Row } from '@tlon/indigo-react';
3+import { Box, Row } from '@tlon/indigo-react';
44
55import useRoller from 'lib/useRoller';
66import { useHistory } from 'store/history';
77import { ReactComponent as InviteIcon } from 'assets/invite.svg';
88import { ReactComponent as HistoryIcon } from 'assets/history.svg';
99import AccountsDropdown from '../Dropdowns/AccountsDropdown';
10−import Modal from '../Modal';
1110import { useRollerStore } from 'store/roller';
1211
1312import './L2PointHeader.scss';
@@ -18,17 +17,18 @@ export interface L2PointHeaderProps {
1817 numInvites: number;
1918 hideTimer: boolean;
2019 hideInvites: boolean;
20+ showMigrate: boolean;
2121}
2222
2323const L2PointHeader = ({
2424 hideTimer = false,
2525 hideInvites = false,
26+ showMigrate = false,
2627 numInvites = 0,
2728}: L2PointHeaderProps) => {
2829 const { config } = useRoller();
2930 const { nextRoll, currentL2, pendingTransactions } = useRollerStore();
3031 const { pointCursor }: any = usePointCursor();
31− const [showModal, setShowModal] = useState(false);
3232
3333 useEffect(() => {
3434 if (isDevelopment) {
@@ -46,10 +46,6 @@ const L2PointHeader = ({
4646 names.TRANSACTION_HISTORY,
4747 push,
4848 ]);
49− const goMigrate = useCallback(() => push(names.MIGRATE_L2), [
50− names.MIGRATE_L2,
51− push,
52− ]);
5349
5450 const isStar =
5551 pointCursor?.value &&
@@ -57,9 +53,9 @@ const L2PointHeader = ({
5753
5854 return (
5955 <Row className="l2-point-header">
60− <AccountsDropdown />
56+ <AccountsDropdown showMigrate={showMigrate} />
6157 <Row className="info">
62− {!hideInvites && (
58+ {!hideInvites && isStar && (
6359 <Row onClick={goToInvites} className="invites">
6460 <InviteIcon />
6561 {numInvites}
@@ -70,36 +66,8 @@ const L2PointHeader = ({
7066 {nextRoll}
7167 </Box>
7268 )}
73− {currentL2 && <HistoryIcon className="history" onClick={goToHistory} />}
74− {!currentL2 && isStar && (
75− <Icon
76− icon="Swap"
77− className="history"
78− onClick={() => setShowModal(true)}
79− />
80− )}
69+ <HistoryIcon className="history" onClick={goToHistory} />
8170 </Row>
82− <Modal show={showModal} hide={() => setShowModal(false)}>
83− <Box className="migrate-modal">
84− <Box className="close" onClick={() => setShowModal(false)}>
85− &#215;
86− </Box>
87− <Box className="title">Migrating to Layer 2</Box>
88− <Box className="message">
89− We've upgraded Bridge to support Layer 2 transactions. If you don't
90− migrate now, you can always do it later.
91− </Box>
92− <Box className="warning">Migrating to Layer 2 is irreversible.</Box>
93− <Row className="buttons">
94− <Button className="cancel" onClick={() => setShowModal(false)}>
95− Cancel
96− </Button>
97− <Button className="migrate" onClick={goMigrate}>
98− Migrate
99− </Button>
100− </Row>
101− </Box>
102− </Modal>
10371 </Row>
10472 );
10573};
src/components/L2/LoadingOverlay.scss+2−1
@@ -1,7 +1,7 @@
11@import 'style/shared/variables.scss';
22
33.loading-overlay {
4− background-color: rgba(0, 0, 0, 0.3);
4+ background-color: rgba(0, 0, 0, 0);
55 position: fixed;
66 top: 0;
77 bottom: 0;
@@ -14,6 +14,7 @@
1414 z-index: 1000;
1515
1616 svg {
17+ display: none;
1718 margin-bottom: 120px;
1819 height: 60px;
1920 width: 60px;
src/lib/useRoller.ts+3−3
@@ -212,8 +212,8 @@ export default function useRoller() {
212212
213213 const getPendingTransactions = useCallback(async () => {
214214 try {
215− const curPoint = need.point(pointCursor);
216− const newPending = await api.getPendingByShip(Number(curPoint));
215+ const curPoint = Number(need.point(pointCursor));
216+ const newPending = await api.getPendingByShip(curPoint);
217217 setPendingTransactions(newPending);
218218
219219 // const allTransactions = await api.getHistory()
@@ -257,7 +257,7 @@ export default function useRoller() {
257257 if (_authToken && _contracts) {
258258 let possibleMissingInvites: number[] = [];
259259 if (isL2) {
260− const allSpawned = await api.getSpawned(Number(curPoint));
260+ const allSpawned = await api.getSpawned(curPoint);
261261 const ownedPoints = maybeGetResult(
262262 controlledPoints,
263263 'ownedPoints',
src/lib/utils/roller.ts+3−2
@@ -2,7 +2,8 @@ export const SECOND = 1000;
22export const MINUTE = SECOND * 60;
33export const HOUR = MINUTE * 60;
44
5−export const padZero = (amount: number) => `${amount < 10 ? '0' : ''}${amount}`;
5+export const padZero = (amount: number) =>
6+ `${amount < 10 && amount > 0 ? '0' : ''}${amount}`;
67
78export const getTimeToNextBatch = (nextBatch: number, now: number) => {
89 const toNext = nextBatch - now;
@@ -12,7 +13,7 @@ export const getTimeToNextBatch = (nextBatch: number, now: number) => {
1213 (toNext - hours * HOUR - minutes * MINUTE) / SECOND
1314 );
1415
15− return `${hours}h ${padZero(minutes)}m ${padZero(seconds)}s`;
16+ return `${padZero(hours)}h ${padZero(minutes)}m ${padZero(seconds)}s`;
1617};
1718
1819export const isL2 = (dom?: string) => dom === 'l2' || dom === 'spawn';
src/store/lib/useControlledPointsStore.js+14−18
@@ -53,24 +53,20 @@ export default function useControlledPointsStore() {
5353 azimuth.azimuth.getSpawningFor(_contracts, address),
5454 ]);
5555
56− // const pointsWithLayers = await Promise.all(
57− // ownedPoints.map(async ship => {
58− // try {
59− // const pointInfo = await api.getPoint(Number(ship));
60− // return {
61− // point: Number(ship),
62− // layer: isL2(pointInfo?.dominion) ? 1 : 2,
63− // };
64− // } catch (error) {
65− // console.warn(error);
66− // return { point: ship, layer: 1 };
67− // }
68− // })
69− // );
70− const pointsWithLayers = ownedPoints.map(point => ({
71− point: Number(point),
72− layer: 1,
73− }));
56+ const pointsWithLayers = await Promise.all(
57+ ownedPoints.map(async ship => {
58+ try {
59+ const pointInfo = await api.getPoint(Number(ship));
60+ return {
61+ point: Number(ship),
62+ layer: isL2(pointInfo?.dominion) ? 2 : 1,
63+ };
64+ } catch (error) {
65+ console.warn(error);
66+ return { point: ship, layer: 1 };
67+ }
68+ })
69+ );
7470
7571 if (
7672 ownedPoints === null &&
src/views/Invite/Cohort.js+14−38
@@ -14,7 +14,6 @@ import { useLocalRouter } from 'lib/LocalRouter';
1414import * as need from 'lib/need';
1515import * as wg from 'lib/walletgen';
1616import useRoller from 'lib/useRoller';
17−import { convertToInt } from 'lib/convertToInt';
1817import { generateUrl } from 'lib/utils/invite';
1918
2019import { useIssueChild } from 'views/Point/IssueChild';
@@ -36,7 +35,7 @@ const INVITES_PER_PAGE = 7;
3635const DEFAULT_NUM_INVITES = 5;
3736
3837export default function InviteCohort() {
39− const { pop, push, names } = useLocalRouter();
38+ const { pop } = useLocalRouter();
4039 const { authToken } = useWallet();
4140 const {
4241 nextRoll,
@@ -48,19 +47,11 @@ export default function InviteCohort() {
4847 const { generateInviteCodes, getPendingTransactions } = useRoller();
4948 const { pointCursor } = usePointCursor();
5049 const { contracts } = useNetwork();
51− const { getDetails, syncControlledPoints } = usePointCache();
50+ const { syncControlledPoints } = usePointCache();
5251
5352 const point = need.point(pointCursor);
5453 const _contracts = need.contracts(contracts);
5554
56− const pointSize = azimuth.azimuth.getPointSize(point);
57− const isParent = pointSize !== azimuth.azimuth.PointSize.Planet;
58− const details = need.details(getDetails(point));
59− const networkRevision = convertToInt(details.keyRevisionNumber, 10);
60− const networkKeysNotSet = !currentL2 && isParent && networkRevision === 0;
61−
62− const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
63−
6455 const [numInvites, setNumInvites] = useState(
6556 currentL2 ? DEFAULT_NUM_INVITES : 1
6657 );
@@ -170,7 +161,7 @@ export default function InviteCohort() {
170161 }, [generateInviteCodes, numInvites, getPendingTransactions, point, pop]);
171162
172163 const getContent = () => {
173− if (hasInvites && !networkKeysNotSet) {
164+ if (hasInvites) {
174165 return (
175166 <>
176167 <div>
@@ -203,18 +194,6 @@ export default function InviteCohort() {
203194 Check back in <span className="timer"> {nextRoll}</span>
204195 </>
205196 );
206− } else if (networkKeysNotSet) {
207− return (
208− <>
209− This star's network keys are not set.
210− <br />
211− Either set the network keys or{' '}
212− <span className="migrate" onClick={() => null}>
213− {/* TODO: change this to navigate to migration */}
214− migrate this star to L2.
215− </span>
216− </>
217− );
218197 }
219198
220199 return (
@@ -224,7 +203,9 @@ export default function InviteCohort() {
224203 {currentL2 && (
225204 <>
226205 Generate your codes in
227− <span className="timer"> {nextRoll} </span>
206+ <br />
207+ <span className="timer">{nextRoll}</span>
208+ <br />
228209 to get in the next roll.
229210 </>
230211 )}
@@ -296,11 +277,16 @@ export default function InviteCohort() {
296277 {currentL2 ? (
297278 <Button
298279 as={'button'}
299− className="generate-codes"
280+ className={`generate-codes ${loading ? 'loading' : ''}`}
300281 center
301282 solid
283+ disabled={loading}
302284 onClick={createInvites}>
303− Generate Planet Code{currentL2 ? `s (${numInvites})` : ''}
285+ {loading
286+ ? 'Generating...'
287+ : `Generate Planet Code${
288+ currentL2 ? `s (${numInvites})` : ''
289+ }`}
304290 </Button>
305291 ) : (
306292 <Grid.Item
@@ -319,7 +305,7 @@ export default function InviteCohort() {
319305 );
320306 }
321307
322− const showGenerateButton = !hasPending && !hasInvites && !networkKeysNotSet;
308+ const showGenerateButton = !hasPending && !hasInvites;
323309
324310 return (
325311 <View
@@ -356,16 +342,6 @@ export default function InviteCohort() {
356342 Generate Codes
357343 </Button>
358344 )}
359− {networkKeysNotSet && (
360− <Button
361− as={'button'}
362− className="generate-button"
363− center
364− solid
365− onClick={goUrbitOS}>
366− Set Network Keys
367− </Button>
368− )}
369345 {hasInvites && (
370346 <Paginator
371347 page={page}
src/views/Invite/Cohort.scss+4
@@ -175,6 +175,10 @@
175175 border: none;
176176 margin-top: 8px;
177177 padding: 8px;
178+
179+ &.loading {
180+ background-color: $mid-gray;
181+ }
178182 }
179183 }
180184 }
src/views/Point/MigrateL2.tsx+12−4
@@ -87,9 +87,7 @@ export default function MigrateL2() {
8787 const [selectedPoint, setSelectedPoint] = useState(point);
8888 const [showDropdown, setShowDropdown] = useState(false);
8989
90− const points = controlledPoints?.value?.value?.pointsWithLayers || [
91− { point, layer: 1 },
92− ];
90+ const points = controlledPoints?.value?.value?.pointsWithLayers || [];
9391 const l1Points = points.filter(
9492 ({ point, layer }: PointLayer) =>
9593 layer === 1 &&
@@ -140,6 +138,16 @@ export default function MigrateL2() {
140138 );
141139
142140 const getContent = () => {
141+ if (l1Points.length === 0) {
142+ return (
143+ <Box className="content">
144+ <Box className="ship-selector">
145+ <Box>All of your ships are already on Layer 2.</Box>
146+ </Box>
147+ </Box>
148+ );
149+ }
150+
143151 if (proceed) {
144152 return (
145153 <Box className="content">
@@ -163,7 +171,7 @@ export default function MigrateL2() {
163171 <Grid.Item
164172 full
165173 as={InlineEthereumTransaction}
166− label="Migrate"
174+ label="Migrate to Layer 2"
167175 {...bind}
168176 onReturn={pop}
169177 />
src/views/Point/Point.js+55−11
@@ -1,9 +1,9 @@
11import React, { useCallback, useEffect, useState } from 'react';
22import cn from 'classnames';
3−import { Just } from 'folktale/maybe';
4−import { Grid, Flex, Button } from 'indigo-react';
5−import { Icon, Row } from '@tlon/indigo-react';
63import { azimuth } from 'azimuth-js';
4+import { Just } from 'folktale/maybe';
5+import { Grid, Flex } from 'indigo-react';
6+import { Box, Icon, Row, Button } from '@tlon/indigo-react';
77
88import { usePointCursor } from 'store/pointCursor';
99import { useWallet } from 'store/wallet';
@@ -32,6 +32,9 @@ import useRoller from 'lib/useRoller';
3232import './Point.scss';
3333import { isL2 } from 'lib/utils/roller';
3434import { isDevelopment } from 'lib/flags';
35+import { convertToInt } from 'lib/convertToInt';
36+import { usePointCache } from 'store/pointCache';
37+import Modal from 'components/L2/Modal';
3538
3639function InviteForm({
3740 showInviteForm,
@@ -123,6 +126,7 @@ export default function Point() {
123126 const { wallet, authToken } = useWallet();
124127 const { pointCursor } = usePointCursor();
125128 const point = need.point(pointCursor);
129+ const { getDetails } = usePointCache();
126130
127131 const { api, getInvites } = useRoller();
128132 const {
@@ -131,9 +135,23 @@ export default function Point() {
131135 invites,
132136 setCurrentPoint,
133137 setPendingTransactions,
138+ currentL2,
134139 } = useRollerStore();
135140
136− const [loaded, setLoaded] = useState(false);
141+ const pointSize = azimuth.getPointSize(point);
142+ const details = need.details(getDetails(point));
143+ const isStarOrGalaxy = pointSize !== azimuth.PointSize.Planet;
144+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
145+ const networkKeysNotSet =
146+ !currentL2 && isStarOrGalaxy && networkRevision === 0;
147+
148+ const [showModal, setShowModal] = useState(networkKeysNotSet);
149+
150+ useEffect(() => {
151+ if (networkKeysNotSet) {
152+ setShowModal(true);
153+ }
154+ }, [networkKeysNotSet]);
137155
138156 useEffect(() => {
139157 const loadL2Info = async () => {
@@ -147,14 +165,11 @@ export default function Point() {
147165 getInvites(isL2(pointInfo.dominion));
148166 };
149167
150− if (!loaded) {
151− getTransactions();
152− setLoaded(true);
153− }
168+ getTransactions();
154169 };
155170
156171 loadL2Info();
157− }, [api, point, authToken, setPendingTransactions, loaded]); // eslint-disable-line
172+ }, [api, point, authToken, setPendingTransactions]); // eslint-disable-line
158173
159174 const { isParent, canManage, canSpawn, canVote } = useCurrentPermissions();
160175
@@ -226,8 +241,13 @@ export default function Point() {
226241 pop={pop}
227242 inset
228243 className="point"
244+ hideBack
229245 header={
230− <L2PointHeader hideTimer={!!numPending} numInvites={invites.length} />
246+ <L2PointHeader
247+ hideTimer={!!numPending}
248+ numInvites={invites.length}
249+ hideInvites={networkKeysNotSet}
250+ />
231251 }>
232252 <Greeting point={point} />
233253 {!!numPending && (
@@ -236,7 +256,10 @@ export default function Point() {
236256 <div className="title">
237257 {numPending} Planet{numPending > 1 ? 's' : ''} Spawned
238258 </div>
239− <div className="rollup-timer">{nextRoll}</div>
259+ <div className="rollup-timer">
260+ <Icon icon="Clock" />
261+ {nextRoll}
262+ </div>
240263 </Row>
241264 <Row className="info-row">
242265 <LayerIndicator layer={2} size="sm" />
@@ -310,6 +333,27 @@ export default function Point() {
310333 )}
311334 {senateButton}
312335 </Grid>
336+ <Modal show={showModal} hide={() => setShowModal(false)}>
337+ <Box className="network-keys-modal">
338+ <Box className="close" onClick={() => setShowModal(false)}>
339+ &#215;
340+ </Box>
341+ <Box className="title">Network Keys Not Set</Box>
342+ <Box className="message">
343+ This point's network keys are not set. The network keys must be set
344+ to spawn points or migrate to Layer 2. Please set them in Urbit OS
345+ Settings.
346+ </Box>
347+ <Row className="buttons">
348+ <Button className="cancel" onClick={() => setShowModal(false)}>
349+ Cancel
350+ </Button>
351+ <Button className="migrate" onClick={goUrbitOS}>
352+ Set Keys
353+ </Button>
354+ </Row>
355+ </Box>
356+ </Modal>
313357 </View>
314358 );
315359}
src/views/Point/Point.scss+67−2
@@ -22,9 +22,19 @@
2222
2323 .rollup-timer {
2424 background-color: $rollup-yellow;
25− padding: 8px;
26− width: 90px;
25+ padding: 4px 8px;
26+ width: 110px;
2727 border-radius: $medium-border-radius;
28+ display: flex;
29+ flex-direction: row;
30+ justify-content: center;
31+ align-items: center;
32+
33+ svg {
34+ height: 14px;
35+ width: 14px;
36+ margin-right: 4px;
37+ }
2838 }
2939 }
3040
@@ -39,3 +49,58 @@
3949 }
4050 }
4151}
52+
53+.network-keys-modal {
54+ padding: 16px;
55+ background-color: white;
56+ border-radius: $medium-border-radius;
57+ line-height: 24px;
58+ position: relative;
59+ width: 368px;
60+
61+ .close {
62+ position: absolute;
63+ top: 3px;
64+ right: 8px;
65+ font-size: 18px;
66+ font-weight: 200;
67+ cursor: pointer;
68+ }
69+
70+ .title {
71+ font-weight: 700;
72+ margin-bottom: 8px;
73+ }
74+
75+ .message {
76+ width: 340px;
77+ }
78+
79+ .buttons {
80+ display: flex;
81+ flex-direction: row;
82+ justify-content: flex-end;
83+ margin-top: 24px;
84+
85+ .cancel, .migrate {
86+ border-radius: $medium-border-radius;
87+ border: none;
88+ height: 32px;
89+ font-family: $default-font;
90+
91+ &:hover {
92+ opacity: 0.8;
93+ }
94+ }
95+
96+ .cancel {
97+ margin-right: 8px;
98+ background-color: $washed-gray;
99+ }
100+
101+ .migrate {
102+ background-color: black;
103+ color: white;
104+ }
105+ }
106+}
src/views/Points.js+1−1
@@ -319,7 +319,7 @@ export default function Points() {
319319 inset
320320 pop={pop}
321321 hideBack
322− header={<L2PointHeader hideTimer hideInvites />}>
322+ header={<L2PointHeader hideTimer hideInvites showMigrate />}>
323323 <NavHeader>
324324 <CopiableAddress
325325 text={address}