on master help sign in
UI and UX updates for bridge
commit3ca083d8c470b460c5dfc12b2f965770abbfb870 authorWill Galebach <willbach@gmail.com> dateMon 1 Nov 2021, 21:01 (5 years ago) parent688345f
34 files changed+601−429
package-lock.json+15−2 package.json+1 src/components/InlineEthereumTransaction.js−8 src/components/L2/Dropdowns/AccountsDropdown.scss+1−1 src/components/L2/Dropdowns/AccountsDropdown.tsx+7−5 src/components/L2/Dropdowns/FeeDropdown.tsx+1−1 src/components/L2/Headers/L2BackHeader.tsx+1−1 src/components/L2/Headers/L2PointHeader.tsx+3−2 src/components/L2/LoadingOverlay.scss+21−3 src/components/L2/LoadingOverlay.tsx+11−2 src/components/NetworkingKeys.js+34−27 src/lib/constants.ts+2 src/lib/types/Invite.ts+4 src/lib/useRoller.ts+70−63 src/lib/utils/invite.ts+13−1 src/lib/utils/point.ts+3 src/lib/utils/roller.ts+19 src/store/network.js+1 src/store/storage/roller.ts+18−15 src/views/Invite/Cohort.scss+6 src/views/Invite/Cohort.tsx+31−36 src/views/Login/LoginSelector.tsx−11 src/views/Point/IssueChild.js+4−1 src/views/Point/MigrateL2.scss+15−12 src/views/Point/MigrateL2.tsx+26−12 src/views/Point/Residents.js+4−4 src/views/UrbitID/DownloadKeys.js+42−35 src/views/UrbitID/ResetKeys.js+15−7 src/views/UrbitID/SetProxy.js+80−71 src/views/UrbitID/Transfer.js+78−68 src/views/UrbitOS/ChangeSponsor.js+35−26 src/views/UrbitOS/Home.tsx+11−4 src/views/UrbitOS/NetworkingKeys.js+19−11 src/views/UrbitOS/UrbitOS.scss+10
package-lock.json+15−2
@@ -11954,6 +11954,11 @@
1195411954 "randomfill": "^1.0.3"
1195511955 }
1195611956 },
11957+ "crypto-js": {
11958+ "version": "3.3.0",
11959+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz",
11960+ "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q=="
11961+ },
1195711962 "crypto-random-string": {
1195811963 "version": "1.0.0",
1195911964 "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
@@ -22480,8 +22485,7 @@
2248022485 "lz-string": {
2248122486 "version": "1.4.4",
2248222487 "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
22483− "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
22484− "dev": true
22488+ "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY="
2248522489 },
2248622490 "magic-string": {
2248722491 "version": "0.25.7",
@@ -29604,6 +29608,15 @@
2960429608 "node-gyp-build": "^4.2.0"
2960529609 }
2960629610 },
29611+ "secure-ls": {
29612+ "version": "1.2.6",
29613+ "resolved": "https://registry.npmjs.org/secure-ls/-/secure-ls-1.2.6.tgz",
29614+ "integrity": "sha512-g8vUSKl6elSfyAUHodybnNkuZW+mUYEOWj4SZIDg+xoQ1dq5ddktBoOFrtxQBUl88ZyAJOtGWQ1PRaOxkTAuZQ==",
29615+ "requires": {
29616+ "crypto-js": "^3.1.6",
29617+ "lz-string": "^1.4.4"
29618+ }
29619+ },
2960729620 "seedrandom": {
2960829621 "version": "3.0.5",
2960929622 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
package.json+1
@@ -74,6 +74,7 @@
7474 "react-teleporter": "^2.2.0",
7575 "react-transition-group": "^4.4.2",
7676 "secp256k1": "^4.0.2",
77+ "secure-ls": "^1.2.6",
7778 "styled-components": "^5.3.0",
7879 "styled-system": "^5.1.5",
7980 "trezor-connect": "^8.1.29",
src/components/InlineEthereumTransaction.js−8
@@ -223,14 +223,6 @@ export default function InlineEthereumTransaction({
223223 {...needFunds}
224224 />
225225 )}
226−
227− {/* {showSignedTx && fakeSigned && (
228− <>
229− <Grid.Item className="mt2 eth-tx-button" full as={NoticeBox}>
230− Your wallet will sign the transaction upon sending it.
231− </Grid.Item>
232− </>
233− )} */}
234226 </>
235227 )}
236228 </BridgeForm>
src/components/L2/Dropdowns/AccountsDropdown.scss+1−1
@@ -4,7 +4,7 @@
44 font-size: 14px;
55 position: relative;
66
7− .selector {
7+ .selector, .dropdown-content .entry.point {
88 font-family: "Source Code Pro", "Roboto mono", "Courier New", monospace;
99 }
1010
src/components/L2/Dropdowns/AccountsDropdown.tsx+7−5
@@ -31,7 +31,7 @@ interface AccountsDropdownProps {
3131const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
3232 const [open, setOpen] = useState<boolean>(false);
3333 const walletInfo: any = useWallet();
34− const { push, names, reset }: any = useHistory();
34+ const { push, popTo, names, reset }: any = useHistory();
3535 const { setPointCursor }: any = usePointCursor();
3636 const { controlledPoints }: any = usePointCache();
3737 const { currentL2 }: any = useRollerStore();
@@ -61,6 +61,8 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
6161 [push, names.POINT, setOpen, setPointCursor]
6262 );
6363
64+ const goHome = useCallback(() => popTo(names.POINTS), [names, popTo]);
65+
6466 const goMigrate = useCallback(() => push(names.MIGRATE_L2), [
6567 names.MIGRATE_L2,
6668 push,
@@ -81,7 +83,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
8183 reset();
8284 };
8385
84− const { canSpawn, isParent } = useCurrentPermissions();
86+ const { canSpawn, isParent, isStar } = useCurrentPermissions();
8587
8688 return (
8789 <Dropdown
@@ -93,7 +95,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
9395 className="current-address"
9496 position={TooltipPosition.Left}
9597 text={currentAddress}>
96− {displayAddress}
98+ <Box onClick={goHome}>{displayAddress}</Box>
9799 </CopiableAddressWrap>
98100 <Box className="divider" />
99101 <Box className="points">
@@ -102,7 +104,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
102104
103105 return (
104106 <Row
105− className="entry"
107+ className="entry point"
106108 onClick={selectPoint(point)}
107109 key={`point-${point}`}>
108110 <Box>{patp}</Box>
@@ -135,7 +137,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
135137 )}
136138 {isParent && canSpawn && (
137139 <Row className="entry" onClick={goIssuePoint}>
138− <Box>Issue Point</Box>
140+ <Box>Spawn{isStar ? ' Planets' : ' Stars'}</Box>
139141 <Icon icon="Plus" />
140142 </Row>
141143 )}
src/components/L2/Dropdowns/FeeDropdown.tsx+1−1
@@ -78,7 +78,7 @@ export default function FeeDropdown({
7878 <Row
7979 className="price"
8080 onClick={selectPrice(value)}
81− key={value.wait}>
81+ key={`gas-price-${ind}`}>
8282 {PRICE_LABELS[ind]}: {formatDisplay(value)}
8383 </Row>
8484 )
src/components/L2/Headers/L2BackHeader.tsx+1−1
@@ -17,7 +17,7 @@ import L2BackButton from './L2BackButton';
1717export interface L2BackHeaderProps {
1818 back?: () => void;
1919 hideBalance?: boolean;
20− className: string;
20+ className?: string;
2121}
2222
2323const L2BackHeader = ({
src/components/L2/Headers/L2PointHeader.tsx+3−2
@@ -47,13 +47,14 @@ const L2PointHeader = ({
4747 push,
4848 ]);
4949
50− const star = pointCursor?.value && isStar(pointCursor.value);
50+ const showInvites =
51+ !hideInvites && Boolean(pointCursor?.value && isStar(pointCursor.value));
5152
5253 return (
5354 <Row className="l2-point-header">
5455 <AccountsDropdown showMigrate={showMigrate} />
5556 <Row className="info">
56− {!hideInvites && star && (
57+ {showInvites && (
5758 <Row onClick={goToInvites} className="invites">
5859 <InviteIcon />
5960 {numInvites}
src/components/L2/LoadingOverlay.scss+21−3
@@ -1,7 +1,7 @@
11@import 'style/shared/variables.scss';
22
33.loading-overlay {
4− background-color: rgba(0, 0, 0, 0);
4+ background-color: rgba(0, 0, 0, 0.3);
55 position: fixed;
66 top: 0;
77 bottom: 0;
@@ -13,9 +13,27 @@
1313 align-items: center;
1414 z-index: 1000;
1515
16+ .solid {
17+ width: 300px;
18+ height: 160px;
19+ background-color: rgb(91, 91, 91);
20+ padding: 1rem 2rem 2rem;
21+ border-radius: 1rem;
22+ display: flex;
23+ flex-direction: column;
24+ align-items: center;
25+ justify-content: center;
26+ }
27+
28+ .loader-text {
29+ color: white;
30+ margin-bottom: 2rem;
31+ width: 200px;
32+ text-align: center;
33+ line-height: 1.5rem;
34+ }
35+
1636 svg {
17− display: none;
18− margin-bottom: 120px;
1937 height: 60px;
2038 width: 60px;
2139 }
src/components/L2/LoadingOverlay.tsx+11−2
@@ -2,14 +2,23 @@ import { Box, LoadingSpinner } from '@tlon/indigo-react';
22
33import './LoadingOverlay.scss';
44
5−const LoadingOverlay = ({ loading = false }: { loading: boolean }) => {
5+const LoadingOverlay = ({
6+ loading = false,
7+ text,
8+}: {
9+ loading: boolean;
10+ text?: string;
11+}) => {
612 if (!loading) {
713 return null;
814 }
915
1016 return (
1117 <Box className="loading-overlay">
12− <LoadingSpinner foreground="white" background="rgba(0,0,0,0.3)" />
18+ <Box className={`${text ? 'solid' : ''}`}>
19+ {text && <Box className="loader-text">{text}</Box>}
20+ <LoadingSpinner foreground="white" background="rgba(0,0,0,0.3)" />
21+ </Box>
1322 </Box>
1423 );
1524};
src/components/NetworkingKeys.js+34−27
@@ -45,7 +45,7 @@ export default function NetworkingKeys({ point }) {
4545 ),
4646 Just: ({ value: key }) => (
4747 <>
48− <Grid.Item full as="code" className="f5 mono wrap">
48+ <Grid.Item full as="div" className="f5 mono">
4949 0x
5050 </Grid.Item>
5151 <Grid.Item full as="code" className="f5 mono wrap">
@@ -58,59 +58,66 @@ export default function NetworkingKeys({ point }) {
5858 );
5959
6060 const renderDetail = (title, value) => (
61− <Flex.Item as={Flex} col>
62− <Flex.Item as={H5} className="gray4">
61+ <Flex.Item as={Flex} row>
62+ <Flex.Item style={{ minWidth: 192, marginBottom: 8 }} as={H5}>
6363 {title}
6464 </Flex.Item>
6565 {value.matchWith({
6666 Nothing: () => (
67− <Flex.Item as={Text} className="f5 gray4">
67+ <Flex.Item as={H5} className="f5 gray4">
6868 Unset
6969 </Flex.Item>
7070 ),
71− Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
71+ Just: ({ value }) => <Flex.Item as={H5}>{value}</Flex.Item>,
7272 })}
7373 </Flex.Item>
7474 );
7575
76+ const revisionNumber = details
77+ .map(d => d.keyRevisionNumber)
78+ .matchWith({
79+ Nothing: () => 0,
80+ Just: ({ value }) => value,
81+ });
82+
83+ const revisionTime = hasKeys
84+ ? ` at ${getRekeyDate(point)
85+ .map(date =>
86+ date.matchWith({
87+ Ok: r => formatDotsWithTime(r.value),
88+ Error: () => 'No date available',
89+ })
90+ )
91+ .matchWith({
92+ Nothing: () => null,
93+ Just: ({ value }) => value,
94+ })}`
95+ : '';
96+
7697 return (
7798 <>
99+ <h5 className="fw-bold">Network Keys</h5>
100+ <h5 className="gray4">
101+ Revision {revisionNumber} {revisionTime}
102+ </h5>
78103 {renderNetworkKeySection(
79− 'Authentication',
104+ 'Authentication Key',
80105 details.chain(chainKeyProp('authenticationKey'))
81106 )}
82107 {renderNetworkKeySection(
83− 'Encryption',
108+ 'Encryption Key',
84109 details.chain(chainKeyProp('encryptionKey'))
85110 )}
86− <Grid.Item full as={Flex} row justify="between" className="mt3">
87− {renderDetail(
88− 'Revision',
89− details.map(d => d.keyRevisionNumber)
90− )}
111+ <Grid.Item full as={Flex} col justify="between" className="mt3">
91112 {renderDetail(
92113 'Continuity Era',
93114 details.map(d => d.continuityNumber)
94115 )}
95116 {renderDetail(
96− 'Crypto Suite Ver.',
117+ 'Crypto Suite Version',
97118 details.map(d => d.cryptoSuiteVersion)
98119 )}
99120 </Grid.Item>
100− {/* TODO: retrieve last rekeydate, if possible? */}
101− {hasKeys && (
102− <Grid.Item full as={Flex} row justify="between" className="mt3">
103− {renderDetail(
104− 'Last Set',
105− getRekeyDate(point).map(date =>
106− date.matchWith({
107− Ok: r => formatDotsWithTime(r.value),
108− Error: () => 'No date available',
109− })
110− )
111− )}
112− </Grid.Item>
113− )}
114121 </>
115122 );
116123}
src/lib/constants.ts+2
@@ -149,6 +149,7 @@ const TRANSACTION_TYPE_TITLES = {
149149 'transfer-point': 'Point Transferred',
150150 'set-management-proxy': 'Management Key Changed',
151151};
152+const DUMMY_L2_ADDRESS = '0x1111111111111111111111111111111111111111';
152153
153154export {
154155 CHECK_BLOCK_EVERY_MS,
@@ -188,4 +189,5 @@ export {
188189 TRANSACTION_STATUS_ICONS,
189190 TRANSACTION_TYPE_ICONS,
190191 TRANSACTION_TYPE_TITLES,
192+ DUMMY_L2_ADDRESS,
191193};
src/lib/types/Invite.ts+4
@@ -4,3 +4,7 @@ export interface Invite {
44 hash: string;
55 owner: string;
66}
7+
8+export interface Invites {
9+ [key: number]: Invite;
10+}
src/lib/useRoller.ts+70−63
@@ -1,14 +1,13 @@
11import { useCallback, useEffect, useMemo, useState } from 'react';
2−import _ from 'lodash';
32import * as azimuth from 'azimuth-js';
43import * as need from 'lib/need';
5−import { Just } from 'folktale/maybe';
4+import SecureLS from 'secure-ls';
65
76import { Invite } from 'lib/types/Invite';
87import { convertToInt } from './convertToInt';
98import { isDevelopment, isRopsten } from './flags';
10−import { hasInvite, generateInviteWallet } from './utils/roller';
11−import { INVITES_PER_PAGE, ROLLER_HOSTS } from './constants';
9+import { generateInviteWallet, getPendingSpawns } from './utils/roller';
10+import { ROLLER_HOSTS } from './constants';
1211
1312import { useNetwork } from 'store/network';
1413import { usePointCursor } from 'store/pointCursor';
@@ -38,8 +37,6 @@ import {
3837 Options,
3938 EthAddress,
4039 UnspawnedPoints,
41− SpawnParams,
42− L2Data,
4340} from '@urbit/roller-api';
4441
4542import {
@@ -50,13 +47,7 @@ import {
5047 registerProxyAddress,
5148} from './utils/roller';
5249
53−function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
54− return (tx as SpawnParams) !== undefined;
55−}
56−
57−function isShipNumber(ship: number | string | undefined): ship is number {
58− return (ship as number) !== undefined;
59−}
50+const ONE_SECOND = 1000;
6051
6152const inviteTemplate = (
6253 planet: number,
@@ -82,6 +73,7 @@ export default function useRoller() {
8273 nextBatchTime,
8374 currentL2,
8475 invitePoints,
76+ invites,
8577 setNextBatchTime,
8678 setNextRoll,
8779 setPendingTransactions,
@@ -131,6 +123,11 @@ export default function useRoller() {
131123 });
132124 }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
133125
126+ const ls = new SecureLS({
127+ isCompression: false,
128+ encryptionSecret: authToken.getOrElse('default'),
129+ });
130+
134131 const generateInviteCodes = useCallback(
135132 async (numInvites: number) => {
136133 const _point = need.point(pointCursor);
@@ -154,25 +151,12 @@ export default function useRoller() {
154151
155152 const pendingTxs = await api.getPendingByShip(_point);
156153
157− const pendingSpawns = _.reduce(
158− pendingTxs,
159− (acc, pending) => {
160− return isSpawn(pending.rawTx?.tx) &&
161− isShipNumber(pending.rawTx?.tx.data.ship)
162− ? acc.add(pending.rawTx?.tx.data.ship!)
163− : acc;
164− },
165− new Set<number>()
166− );
154+ const pendingSpawns = getPendingSpawns(pendingTxs);
167155
168156 planets = planets.filter((point: number) => !pendingSpawns.has(point));
169157
170158 const starInfo = await api.getPoint(_point);
171− const tickets: {
172− ticket: string;
173− planet: number;
174− owner: string;
175− }[] = [];
159+ const invites: Invite[] = [];
176160 const requests: Promise<string>[] = [];
177161
178162 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
@@ -244,12 +228,15 @@ export default function useRoller() {
244228 );
245229 await Promise.all(requests);
246230
247− tickets.push({
231+ invites.push({
232+ hash: '',
248233 ticket,
249234 planet,
250235 owner: inviteWallet.ownership.keys.address,
251236 });
252237 }
238+
239+ setStoredInvites(ls, invites);
253240 },
254241 [
255242 api,
@@ -259,7 +246,7 @@ export default function useRoller() {
259246 wallet,
260247 web3,
261248 getDetails,
262− authMnemonic,
249+ ls,
263250 setInviteGeneratingNum,
264251 ]
265252 );
@@ -290,23 +277,25 @@ export default function useRoller() {
290277 const newPending = await api.getPendingByShip(curPoint);
291278
292279 setPendingTransactions(newPending);
293−
294− // const allTransactions = await api.getHistory()
295280 } catch (error) {
296281 console.warn('ERROR GETTING PENDING', error);
297282 }
298283 }, [api, setPendingTransactions, pointCursor]);
299284
285+ const generateInviteInfo = async (planet: number, _authToken: string) => {
286+ const { ticket, inviteWallet } = await generateInviteWallet(
287+ planet,
288+ _authToken
289+ );
290+
291+ return inviteTemplate(planet, ticket, inviteWallet.ownership.keys.address);
292+ };
293+
300294 const getInvites = useCallback(
301− async (isL2: boolean, page: number, getAll = false) => {
295+ async (isL2: boolean, getAll = false) => {
302296 try {
303297 const curPoint: number = Number(need.point(pointCursor));
304− const storedInvites = getStoredInvites(curPoint);
305− if (storedInvites.length) {
306− setInvites(storedInvites);
307− } else {
308− setInvites(invitePoints.map(p => inviteTemplate(p)));
309− }
298+ setInvites(invitePoints.map(p => inviteTemplate(p)));
310299
311300 const _authToken = authToken.getOrElse(null);
312301 const _contracts = contracts.getOrElse(null);
@@ -332,38 +321,27 @@ export default function useRoller() {
332321
333322 const newInvites: Invite[] = [];
334323 // Iterate over all of the stored invites, generating wallet info as necessary
324+ const storedInvites = getStoredInvites(ls);
325+
335326 for (let i = 0; i < invitePlanets.length; i++) {
336327 setInviteGeneratingNum(i + 1);
337328 const planet = invitePlanets[i];
338− const invite = storedInvites.find(hasInvite(planet));
339−
340− const start = page * INVITES_PER_PAGE;
341− const end = start + INVITES_PER_PAGE;
329+ const invite = storedInvites[planet];
342330
343− if (invite) {
331+ if (invite?.ticket) {
344332 newInvites.push(invite);
345− } else if (getAll || (i >= start && i < end)) {
333+ } else if (getAll) {
346334 if (isDevelopment) {
347335 console.log('MISSING INVITE INFO', planet);
348336 }
349− const { ticket, inviteWallet } = await generateInviteWallet(
350− planet,
351− _authToken
352− );
353−
354− newInvites.push(
355− inviteTemplate(
356− planet,
357− ticket,
358− inviteWallet.ownership.keys.address
359− )
360− );
337+ const newInvite = await generateInviteInfo(planet, _authToken);
338+ newInvites.push(newInvite);
361339 } else {
362340 newInvites.push(inviteTemplate(planet));
363341 }
364342 }
365343
366− setStoredInvites(curPoint, newInvites);
344+ setStoredInvites(ls, newInvites);
367345 setInvites(newInvites);
368346
369347 return newInvites;
@@ -380,11 +358,28 @@ export default function useRoller() {
380358 getDetails,
381359 pointCursor,
382360 invitePoints,
361+ ls,
383362 setInvites,
384363 setInviteGeneratingNum,
385364 ]
386365 );
387366
367+ const showInvite = useCallback(
368+ async (planet: number) => {
369+ const _authToken = authToken.getOrElse(null);
370+
371+ if (_authToken) {
372+ const newInvite = await generateInviteInfo(planet, _authToken);
373+ const newInvites = invites.map(invite =>
374+ invite.planet === planet ? newInvite : invite
375+ );
376+ setInvites(newInvites);
377+ setStoredInvites(ls, newInvites);
378+ }
379+ },
380+ [authToken, invites, ls, setInvites]
381+ );
382+
388383 const getNumInvites = useCallback(
389384 async (isL2: boolean) => {
390385 try {
@@ -400,12 +395,22 @@ export default function useRoller() {
400395 curPoint
401396 );
402397
398+ const newPending = await api.getPendingByShip(curPoint);
399+ const pendingSpawns = getPendingSpawns(newPending);
400+
403401 const outgoingPoints = getOutgoingPoints(
404402 controlledPoints,
405403 getDetails
406404 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
407405
408− setInvitePoints(invitePoints.concat(outgoingPoints));
406+ console.log(pendingSpawns, outgoingPoints, invitePoints)
407+
408+ setPendingTransactions(newPending);
409+ setInvitePoints(
410+ invitePoints
411+ .concat(outgoingPoints)
412+ .filter(p => !pendingSpawns.has(p))
413+ );
409414 }
410415 } catch (e) {
411416 if (isDevelopment) {
@@ -416,11 +421,12 @@ export default function useRoller() {
416421 [
417422 api,
418423 pointCursor,
419− setInvitePoints,
420424 authToken,
421425 contracts,
422426 controlledPoints,
423427 getDetails,
428+ setInvitePoints,
429+ setPendingTransactions,
424430 ]
425431 );
426432
@@ -645,12 +651,11 @@ export default function useRoller() {
645651 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
646652 setNextRoll(nextRoll);
647653
648− if (nextBatchTime <= new Date().getTime()) {
654+ if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
649655 api.getRollerConfig().then(response => {
650656 setNextBatchTime(response.nextBatch);
657+ getNumInvites(currentL2);
651658 });
652−
653− getInvites(currentL2, 0);
654659 }
655660 }, 1000);
656661
@@ -669,5 +674,7 @@ export default function useRoller() {
669674 setProxyAddress,
670675 configureNetworkingKeys,
671676 getNumInvites,
677+ showInvite,
678+ ls,
672679 };
673680}
src/lib/utils/invite.ts+13−1
@@ -2,7 +2,19 @@ import * as ob from 'urbit-ob';
22
33export const generateUrl = (ticket: string, planet: number) => {
44 if (ticket && planet) {
5− return `urb.it/#${ticket.slice(1)}-${ob.patp(planet).slice(1)}`;
5+ return `bridge.urbit.org/#${ticket.slice(1)}-${ob.patp(planet).slice(1)}`;
6+ } else if (planet) {
7+ return ob.patp(planet);
8+ }
9+
10+ return 'No Ticket or Ship';
11+};
12+
13+export const generateUrlAbbreviation = (ticket: string, planet: number) => {
14+ if (ticket && planet) {
15+ return `bridge.urbit.org/#${ticket.slice(1, 7)}...${ob
16+ .patp(planet)
17+ .slice(1)}`;
618 } else if (planet) {
719 return ob.patp(planet);
820 }
src/lib/utils/point.ts+3
@@ -1,5 +1,8 @@
11import { azimuth } from 'azimuth-js';
22
3+export const isGalaxy = (point: number) =>
4+ azimuth.getPointSize(point) === azimuth.PointSize.Galaxy;
5+
36export const isStar = (point: number) =>
47 azimuth.getPointSize(point) === azimuth.PointSize.Star;
58
src/lib/utils/roller.ts+19
@@ -10,6 +10,9 @@ import RollerRPCAPI, {
1010 EthAddress,
1111 AddressParams,
1212 Ship,
13+ SpawnParams,
14+ L2Data,
15+ PendingTransaction,
1316} from '@urbit/roller-api';
1417
1518import { deriveNetworkKeys, CRYPTO_SUITE_VERSION } from 'lib/keys';
@@ -200,3 +203,19 @@ export const reticketL2Point = async () => {};
200203
201204export const hasInvite = (point: number) => (invite: Invite) =>
202205 invite.planet === point && invite.ticket.length > 0;
206+
207+function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
208+ return (tx as SpawnParams) !== undefined;
209+}
210+
211+function isShipNumber(ship: number | string | undefined): ship is number {
212+ return (ship as number) !== undefined;
213+}
214+
215+export const getPendingSpawns = (pendingTxs: PendingTransaction[]) =>
216+ pendingTxs.reduce((acc, pending) => {
217+ return isSpawn(pending.rawTx?.tx) &&
218+ isShipNumber(pending.rawTx?.tx.data.ship)
219+ ? acc.add(pending.rawTx?.tx.data.ship!)
220+ : acc;
221+ }, new Set<number>());
src/store/network.js+1
@@ -84,6 +84,7 @@ function _useNetwork(initialNetworkType = null) {
8484 }
8585 case NETWORK_TYPES.ROPSTEN: {
8686 const endpoint = `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
87+ console.log('ENDPOINT', endpoint)
8788
8889 initWeb3(
8990 new Web3.providers.HttpProvider(endpoint),
src/store/storage/roller.ts+18−15
@@ -1,24 +1,27 @@
1−import { Invite } from 'lib/types/Invite';
1+import SecureLS from 'secure-ls';
2+import { Invite, Invites } from 'lib/types/Invite';
23
34const INVITES_KEY = 'invites';
45const HIDE_MIGRATION_MESSAGE_KEY = 'hide_migration_message';
56
6−export const getStoredInvites = (point: number): Invite[] => {
7− const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
8− const invites = JSON.parse(invitesString);
9−
10− if (invites[String(point)]) {
11− return invites[String(point)];
12− }
13−
14− return [];
7+export const getStoredInvites = (ls: SecureLS): Invites => {
8+ let invites = {};
9+ try {
10+ invites = ls.get(INVITES_KEY);
11+ } catch (e) {}
12+ return invites;
1513};
1614
17−export const setStoredInvites = (point: number, newInvites: Invite[]) => {
18− const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
19− const invites = JSON.parse(invitesString);
20− invites[Number(point)] = newInvites;
21− localStorage.setItem(INVITES_KEY, JSON.stringify(invites));
15+export const setStoredInvites = (ls: SecureLS, newInvites: Invite[]) => {
16+ let invites: Invites = {};
17+ try {
18+ invites = ls.get(INVITES_KEY);
19+ } catch (e) {}
20+
21+ newInvites.forEach(invite => {
22+ invites[Number(invite.planet)] = invite;
23+ });
24+ ls.set(INVITES_KEY, invites);
2225};
2326
2427export const clearInvitesStorage = () => localStorage.removeItem(INVITES_KEY);
src/views/Invite/Cohort.scss+6
@@ -77,6 +77,10 @@
7777 border: 1px solid $light-gray;
7878 width: 390px;
7979 padding: 3px 8px;
80+
81+ &.shortened {
82+ width: 300px;
83+ }
8084 }
8185
8286 .copy-invite, .ticket-loader {
@@ -111,6 +115,8 @@
111115 .generate-button {
112116 margin-bottom: 20px;
113117 border-radius: $medium-border-radius;
118+ color: white;
119+ background-color: black;
114120 }
115121 }
116122
src/views/Invite/Cohort.tsx+31−36
@@ -1,11 +1,12 @@
11import React, { useEffect, useCallback, useState } from 'react';
22import { Grid, Button } from 'indigo-react';
33import * as azimuth from 'azimuth-js';
4+import { Just } from 'folktale/maybe';
45import {
56 Icon,
6− LoadingSpinner,
77 Row,
88 StatelessTextInput,
9+ Button as Button2,
910} from '@tlon/indigo-react';
1011
1112import { usePointCursor } from 'store/pointCursor';
@@ -16,10 +17,10 @@ import { useRollerStore } from 'store/roller';
1617
1718import { useLocalRouter } from 'lib/LocalRouter';
1819import * as need from 'lib/need';
19−import * as wg from 'lib/walletgen';
2020import useRoller from 'lib/useRoller';
2121import {
2222 generateUrl,
23+ generateUrlAbbreviation,
2324 generateCsvLine,
2425 generateCsvName,
2526} from 'lib/utils/invite';
@@ -44,6 +45,7 @@ import {
4445 DEFAULT_CSV_NAME,
4546} from 'lib/constants';
4647import { isPlanet } from 'lib/utils/point';
48+import { generateInviteWallet } from 'lib/utils/roller';
4749
4850interface L1Invite {
4951 ticket: string;
@@ -61,18 +63,19 @@ export default function InviteCohort() {
6163 invitePoints,
6264 setInviteGeneratingNum,
6365 setInvites,
64− // inviteGeneratingNum,
6566 } = useRollerStore();
6667 const {
68+ ls,
6769 generateInviteCodes,
6870 getPendingTransactions,
6971 getInvites,
72+ showInvite,
7073 } = useRoller();
7174 const { pointCursor }: any = usePointCursor();
7275 const { contracts }: any = useNetwork();
7376 const { syncControlledPoints }: any = usePointCache();
7477
75− const point = need.point(pointCursor);
78+ const point = Just(pointCursor)
7679 const _contracts = need.contracts(contracts);
7780
7881 const [numInvites, setNumInvites] = useState(
@@ -89,7 +92,7 @@ export default function InviteCohort() {
8992
9093 useEffect(() => {
9194 if (invitePoints.length > 0) {
92− getInvites(currentL2, page);
95+ getInvites(currentL2);
9396 }
9497 }, []); // eslint-disable-line react-hooks/exhaustive-deps
9598
@@ -118,12 +121,12 @@ export default function InviteCohort() {
118121 }
119122
120123 if (invitePoint) {
121− const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
124+ const { ticket, inviteWallet } = await generateInviteWallet(
122125 invitePoint,
123126 _authToken
124127 );
125128
126− construct(invitePoint, owner.keys.address);
129+ construct(invitePoint, inviteWallet.keys.address);
127130 setL1Invite({ ticket, planet: invitePoint });
128131 } else {
129132 setError('No available planets');
@@ -149,13 +152,9 @@ export default function InviteCohort() {
149152 ]);
150153
151154 useEffect(() => {
152− const storedInvites = getStoredInvites(point);
155+ const storedInvites = getStoredInvites(ls);
153156
154− if (
155− !completed ||
156− !l1Invite ||
157− storedInvites.find(({ planet }) => planet === l1Invite.planet)
158− ) {
157+ if (!completed || !l1Invite || storedInvites[l1Invite.planet]) {
159158 return;
160159 }
161160
@@ -200,7 +199,7 @@ export default function InviteCohort() {
200199 const downloadCsv = useCallback(() => {
201200 const generateAndDownload = async () => {
202201 setDownloadingCsv(true);
203− const invites = await getInvites(currentL2, page, true);
202+ const invites = await getInvites(currentL2, true);
204203 if (invites) {
205204 const csv = invites.reduce(
206205 (csvData, { ticket, planet }, ind) =>
@@ -229,14 +228,12 @@ export default function InviteCohort() {
229228 setDownloadingCsv,
230229 setInviteGeneratingNum,
231230 currentL2,
232− page,
233231 point,
234232 ]);
235233
236234 const goNextPage = useCallback(() => {
237235 setPage(page + 1);
238− getInvites(currentL2, page + 1);
239− }, [currentL2, getInvites, page]);
236+ }, [page]);
240237
241238 const getContent = () => {
242239 if (hasInvites) {
@@ -252,8 +249,9 @@ export default function InviteCohort() {
252249 <div className="invites">
253250 {invitesToDisplay.map(invite => (
254251 <div className="invite" key={invite.planet}>
255− <div className="invite-url">
256− {generateUrl(invite.ticket, invite.planet)}
252+ <div
253+ className={`invite-url ${invite.ticket ? '' : 'shortened'}`}>
254+ {generateUrlAbbreviation(invite.ticket, invite.planet)}
257255 </div>
258256 {invite.ticket ? (
259257 <CopiableWithTooltip
@@ -261,9 +259,11 @@ export default function InviteCohort() {
261259 className="copy-invite"
262260 />
263261 ) : (
264− <div className="ticket-loader">
265− <LoadingSpinner foreground="black" background="white" />
266− </div>
262+ <Button2
263+ className="secondary"
264+ onClick={() => showInvite(invite.planet)}>
265+ Show Invite
266+ </Button2>
267267 )}
268268 </div>
269269 ))}
@@ -354,9 +354,8 @@ export default function InviteCohort() {
354354 <Button
355355 as={'button'}
356356 className={`generate-codes ${loading ? 'loading' : ''}`}
357− center
358− solid
359357 disabled={loading}
358+ center
360359 onClick={createInvites}>
361360 {loading
362361 ? `Generating ${numInvites} invites...`
@@ -382,6 +381,9 @@ export default function InviteCohort() {
382381 }
383382
384383 const showGenerateButton = !hasPending && !hasInvites;
384+ const downloadingCsvText = `Generating ${
385+ invitePoints.length
386+ } codes, this could take up to ${invitePoints.length * 5} seconds...`;
385387
386388 return (
387389 <View
@@ -397,16 +399,10 @@ export default function InviteCohort() {
397399 ) : (
398400 <Row className="has-invites-header">
399401 {header}
400− {downloadingCsv ? (
401− <Row className="download-csv">
402− Generating {invitePoints.length} codes...
403− </Row>
404− ) : (
405− <Row className="download-csv" onClick={downloadCsv}>
406− <Icon icon="Download" />
407− <div>CSV</div>
408− </Row>
409− )}
402+ <Row className="download-csv" onClick={downloadCsv}>
403+ <Icon icon="Download" />
404+ <div>CSV</div>
405+ </Row>
410406 </Row>
411407 )}
412408 </HeaderPane>
@@ -416,10 +412,8 @@ export default function InviteCohort() {
416412 </div>
417413 {showGenerateButton && (
418414 <Button
419− as={'button'}
420415 className="generate-button"
421416 center
422− solid
423417 onClick={() => setShowInviteForm(true)}>
424418 Generate Codes
425419 </Button>
@@ -443,6 +437,7 @@ export default function InviteCohort() {
443437 Add More
444438 </Button>
445439 )}
440+ <LoadingOverlay loading={downloadingCsv} text={downloadingCsvText} />
446441 </View>
447442 );
448443}
src/views/Login/LoginSelector.tsx−11
@@ -59,7 +59,6 @@ export default function LoginSelector({
5959 }: any = useWallet();
6060
6161 const { setPointCursor }: any = usePointCursor();
62− const { authenticate } = useWalletConnect();
6362 const { web3 }: any = useNetwork();
6463 const _web3 = need.web3(web3);
6564
@@ -68,14 +67,6 @@ export default function LoginSelector({
6867 names.ACTIVATE,
6968 ]);
7069
71− const connectWalletConnect = async () => {
72− resetWallet();
73− setWalletType(WALLET_TYPES.WALLET_CONNECT);
74− setPointCursor(Nothing());
75− await authenticate();
76− goHome();
77− };
78−
7970 const connectMetamask = useCallback(async () => {
8071 try {
8172 resetWallet();
@@ -114,8 +105,6 @@ export default function LoginSelector({
114105 const selectOption = (value: string) => () => {
115106 if (value === NAMES.METAMASK) {
116107 connectMetamask();
117− } else if (value === NAMES.WALLET_CONNECT) {
118− connectWalletConnect();
119108 } else {
120109 setCurrentTab(value);
121110 }
src/views/Point/IssueChild.js+4−1
@@ -31,6 +31,7 @@ import BridgeForm from 'form/BridgeForm';
3131import FormError from 'form/FormError';
3232import CopiableAddress from 'components/copiable/CopiableAddress';
3333import { convertToInt } from 'lib/convertToInt';
34+import useCurrentPermissions from 'lib/useCurrentPermissions';
3435
3536export function useIssueChild() {
3637 const { contracts } = useNetwork();
@@ -128,11 +129,13 @@ export default function IssueChild() {
128129 [construct, unconstruct]
129130 );
130131
132+ const { isStar } = useCurrentPermissions();
133+
131134 return (
132135 <View pop={pop} inset>
133136 <Grid>
134137 <Grid.Item full as={ViewHeader}>
135− Issue Child Point
138+ Spawn {isStar ? ' Planets' : ' Stars'}
136139 </Grid.Item>
137140
138141 {isDefaultState && (
src/views/Point/MigrateL2.scss+15−12
@@ -147,25 +147,28 @@
147147 display: flex;
148148 flex-direction: row;
149149
150− .transfer, .spawn {
150+ .transfer {
151151 margin: 16px 0px;
152152 cursor: pointer;
153− border-radius: $medium-border-radius;
154− color: white;
155− background-color: $light-gray;
156153 padding: 4px 8px;
154+ display: flex;
155+ flex-direction: row;
156+ align-items: center;
157157
158− &:hover {
159− opacity: 0.8;
160− }
161−
162− &.selected {
163− background-color: black;
158+ .radio {
159+ margin-right: 8px;
160+ height: 16px;
161+ width: 16px;
162+ border-color: black;
164163
165− &:hover {
166− opacity: 1;
164+ &.selected {
165+ background-color: gray;
167166 }
168167 }
168+
169+ &:hover {
170+ opacity: 0.8;
171+ }
169172 }
170173
171174 .transfer {
src/views/Point/MigrateL2.tsx+26−12
@@ -15,8 +15,14 @@ import * as need from 'lib/need';
1515
1616import { usePointCursor } from 'store/pointCursor';
1717
18−import './MigrateL2.scss';
19−import { Box, Button, Checkbox, Icon, Row } from '@tlon/indigo-react';
18+import {
19+ Box,
20+ Button,
21+ Checkbox,
22+ Icon,
23+ RadioButton,
24+ Row,
25+} from '@tlon/indigo-react';
2026import {
2127 getHideMigrationMessage,
2228 storeHideMigrationMessage,
@@ -25,13 +31,13 @@ import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2531import { useNetwork } from 'store/network';
2632import { usePointCache } from 'store/pointCache';
2733import useEthereumTransaction from 'lib/useEthereumTransaction';
28−import { GAS_LIMITS } from 'lib/constants';
34+import { DUMMY_L2_ADDRESS, GAS_LIMITS } from 'lib/constants';
2935import Dropdown from 'components/L2/Dropdowns/Dropdown';
3036import { PointLayer } from 'lib/types/PointLayer';
3137import Sigil from 'components/Sigil';
3238import { isPlanet, isStar } from 'lib/utils/point';
3339
34−const DUMMY_L2_ADDRESS = '0x1111111111111111111111111111111111111111';
40+import './MigrateL2.scss';
3541
3642const PointEntry = ({
3743 point,
@@ -186,16 +192,24 @@ export default function MigrateL2() {
186192 </Dropdown>
187193 {star && (
188194 <Box className="transfer-spawn-selector">
189− <Box
190− onClick={toggleTransfer}
191− className={`transfer ${transfer ? 'selected' : ''}`}>
195+ <Row onClick={toggleTransfer} className="transfer">
196+ <RadioButton
197+ color="rgba(0,0,0,0.3)"
198+ className={`radio ${transfer ? 'selected' : ''}`}
199+ selected={transfer}
200+ name="transferType"
201+ />
192202 Transfer Point
193− </Box>
194− <Box
195− onClick={toggleTransfer}
196− className={`spawn ${!transfer ? 'selected' : ''}`}>
203+ </Row>
204+ <Row onClick={toggleTransfer} className="transfer">
205+ <RadioButton
206+ color="rgba(0,0,0,0.3)"
207+ className={`radio ${!transfer ? 'selected' : ''}`}
208+ selected={!transfer}
209+ name="transferType"
210+ />
197211 Set Spawn Proxy
198− </Box>
212+ </Row>
199213 </Box>
200214 )}
201215 {star && (
src/views/Point/Residents.js+4−4
@@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
22import cn from 'classnames';
33import { Grid, H4, HelpText } from 'indigo-react';
44import { Just, Nothing } from 'folktale/maybe';
5−import { azimuth } from 'azimuth-js';
65import * as ob from 'urbit-ob';
76
87import View from 'components/View';
@@ -21,6 +20,7 @@ import * as need from 'lib/need';
2120import { usePointCursor } from 'store/pointCursor';
2221import { usePointCache } from 'store/pointCache';
2322import Footer from 'components/Footer';
23+import { isGalaxy } from 'lib/utils/point';
2424
2525const NAMES = {
2626 REQUESTS: 'REQUESTS',
@@ -209,10 +209,10 @@ export default function Residents() {
209209 const onDecline = isRequests && _onDecline;
210210
211211 const points = isRequests ? requests : residents;
212− const { canSpawn, canVote } = useCurrentPermissions();
212+ const { canSpawn, canVote, isStar } = useCurrentPermissions();
213213
214214 const senateButton = (() => {
215− if (azimuth.getPointSize(point) !== azimuth.PointSize.Galaxy) {
215+ if (!isGalaxy(point)) {
216216 return null;
217217 }
218218 return (
@@ -246,7 +246,7 @@ export default function Residents() {
246246 as={ForwardButton}
247247 disabled={!canSpawn}
248248 onClick={goIssuePoint}>
249− Issue Point
249+ Spawn {isStar ? ' Planets' : ' Stars'}
250250 </Grid.Item>
251251 <Grid.Divider />
252252 {senateButton}
src/views/UrbitID/DownloadKeys.js+42−35
@@ -1,6 +1,7 @@
11import React, { useCallback, useState } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
33import * as need from 'lib/need';
4+import { Row } from '@tlon/indigo-react';
45
56import { usePointCursor } from 'store/pointCursor';
67
@@ -12,8 +13,10 @@ import useKeyfileGenerator from 'lib/useKeyfileGenerator';
1213
1314import { useWallet } from 'store/wallet';
1415import { Grid, P } from 'indigo-react';
15−import ViewHeader from 'components/ViewHeader';
1616import { useLocalRouter } from 'lib/LocalRouter';
17+import Window from 'components/L2/Window/Window';
18+import HeaderPane from 'components/L2/Window/HeaderPane';
19+import BodyPane from 'components/L2/Window/BodyPane';
1720
1821export default function AdminRedownload() {
1922 const { urbitWallet } = useWallet();
@@ -36,40 +39,44 @@ export default function AdminRedownload() {
3639 const goBack = useCallback(() => pop(), [pop]);
3740
3841 return (
39− <>
40− <Grid>
41− <Grid.Item full as={ViewHeader}>
42− Download Passport
43− </Grid.Item>
44− <Grid.Item full as={P}>
45− After you’ve downloaded your passport, back up the ticket manually or
46− store on a trusted device.
47− </Grid.Item>
48− {downloaded ? (
49− <Grid.Item full as={RestartButton} solid onClick={goBack}>
50− Done
42+ <Window>
43+ <HeaderPane>
44+ <Row className="header-row">
45+ <h5>Download Passport</h5>
46+ </Row>
47+ </HeaderPane>
48+ <BodyPane>
49+ <Grid>
50+ <Grid.Item full as={P}>
51+ After you’ve downloaded your passport, back up the ticket manually
52+ or store on a trusted device.
5153 </Grid.Item>
52− ) : (
53− <Grid.Item
54− full
55− as={DownloadButton}
56− solid
57− disabled={Nothing.hasInstance(paper)}
58− onClick={doDownload}>
59− {paper.matchWith({
60− Nothing: () => 'Printing and folding...',
61− Just: _ => 'Download paper wallet',
62− })}
63− </Grid.Item>
64− )}
65− </Grid>
66− <PaperBuilder
67− point={point}
68− wallets={[_urbitWallet]}
69− callback={paper => {
70− setPaper(Just(paper));
71− }}
72− />
73− </>
54+ {downloaded ? (
55+ <Grid.Item full as={RestartButton} solid onClick={goBack}>
56+ Done
57+ </Grid.Item>
58+ ) : (
59+ <Grid.Item
60+ full
61+ as={DownloadButton}
62+ solid
63+ disabled={Nothing.hasInstance(paper)}
64+ onClick={doDownload}>
65+ {paper.matchWith({
66+ Nothing: () => 'Printing and folding...',
67+ Just: _ => 'Download paper wallet',
68+ })}
69+ </Grid.Item>
70+ )}
71+ </Grid>
72+ <PaperBuilder
73+ point={point}
74+ wallets={[_urbitWallet]}
75+ callback={paper => {
76+ setPaper(Just(paper));
77+ }}
78+ />
79+ </BodyPane>
80+ </Window>
7481 );
7582}
src/views/UrbitID/ResetKeys.js+15−7
@@ -11,6 +11,10 @@ import ResetConfirm from './ResetKeys/ResetConfirm';
1111import ResetDownload from './ResetKeys/ResetDownload';
1212import ResetVerify from './ResetKeys/ResetVerify';
1313import ResetExecute from './ResetKeys/ResetExecute';
14+import Window from 'components/L2/Window/Window';
15+import HeaderPane from 'components/L2/Window/HeaderPane';
16+import BodyPane from 'components/L2/Window/BodyPane';
17+import { Row } from '@tlon/indigo-react';
1418
1519const NAMES = {
1620 CONFIRM: 'CONFIRM',
@@ -50,13 +54,17 @@ export default function ResetKeys() {
5054 );
5155
5256 return (
53− <LocalRouterProvider value={router}>
54− <Grid>
55− <Grid.Item full as={Flex} row align="center">
57+ <Window>
58+ <HeaderPane>
59+ <Row className="header-row">
5660 <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
57− </Grid.Item>
58− </Grid>
59− <Route newWallet={newWallet} setNewWallet={setNewWallet} />
60− </LocalRouterProvider>
61+ </Row>
62+ </HeaderPane>
63+ <BodyPane>
64+ <LocalRouterProvider value={router}>
65+ <Route newWallet={newWallet} setNewWallet={setNewWallet} />
66+ </LocalRouterProvider>
67+ </BodyPane>
68+ </Window>
6169 );
6270}
src/views/UrbitID/SetProxy.js+80−71
@@ -32,6 +32,10 @@ import {
3232} from 'form/validators';
3333import BridgeForm from 'form/BridgeForm';
3434import FormError from 'form/FormError';
35+import Window from 'components/L2/Window/Window';
36+import HeaderPane from 'components/L2/Window/HeaderPane';
37+import BodyPane from 'components/L2/Window/BodyPane';
38+import { Row } from '@tlon/indigo-react';
3539
3640const getL2ProxyName = proxy =>
3741 proxy === PROXY_TYPE.MANAGEMENT
@@ -207,83 +211,88 @@ export default function SetProxy() {
207211 }, [completed, properProxyType]);
208212
209213 return (
210− <Grid>
211− <Grid.Item full className="f5 mb2">
212− {header}
213− </Grid.Item>
214−
215− {!completed && (
216− <>
217− <Grid.Item full as={Text} className="mb4 f5 gray4">
218− {proxyTypeToHumanDescription(data.proxyType)}
219− </Grid.Item>
220− <Grid.Item
221− full
222− as={Text}
223− className={cn('f6', {
224− green3: completed,
225− })}>
226− Current
227− </Grid.Item>
228− </>
229− )}
230−
231− <BridgeForm
232− validate={validate}
233− onValues={onValues}
234− initialValues={initialValues}>
235− {({ handleSubmit, values }) => (
236− <>
237− <Grid.Item full as={Flex} row justify="between" align="center">
238− <Flex.Item
239− flex
214+ <Window>
215+ <HeaderPane>
216+ <Row className="header-row">
217+ <h5>{header}</h5>
218+ </Row>
219+ </HeaderPane>
220+ <BodyPane>
221+ <Grid style={{ width: '100%' }}>
222+ {!completed && (
223+ <>
224+ <Grid.Item full as={Text} className="mb4 f5 gray4">
225+ {proxyTypeToHumanDescription(data.proxyType)}
226+ </Grid.Item>
227+ <Grid.Item
228+ full
240229 as={Text}
241− className={cn('mono mv3 gray4', {
242− black: !completed && isProxySet,
243− gray4: !completed && !isProxySet,
230+ className={cn('f6', {
244231 green3: completed,
245232 })}>
246− {isProxySet ? proxyAddress : 'Unset'}
247− </Flex.Item>
248− </Grid.Item>
233+ Current
234+ </Grid.Item>
235+ </>
236+ )}
249237
250− {completed ? (
251− <Grid.Item full className="mb4" />
252− ) : (
253− <Grid.Item
254− full
255− as={AddressInput}
256− className="mv4"
257− name="address"
258− label={`New ${properProxyType} Address`}
259− disabled={inputsLocked || values.unset}
260− />
261− )}
238+ <BridgeForm
239+ validate={validate}
240+ onValues={onValues}
241+ initialValues={initialValues}>
242+ {({ handleSubmit, values }) => (
243+ <>
244+ <Grid.Item full as={Flex} row justify="between" align="center">
245+ <Flex.Item
246+ flex
247+ as={Text}
248+ className={cn('mono mv3 gray4', {
249+ black: !completed && isProxySet,
250+ gray4: !completed && !isProxySet,
251+ green3: completed,
252+ })}>
253+ {isProxySet ? proxyAddress : 'Unset'}
254+ </Flex.Item>
255+ </Grid.Item>
262256
263− <Grid.Item full as={FormError} />
257+ {completed ? (
258+ <Grid.Item full className="mb4" />
259+ ) : (
260+ <Grid.Item
261+ full
262+ as={AddressInput}
263+ className="mv4"
264+ name="address"
265+ label={`New ${properProxyType} Address`}
266+ disabled={inputsLocked || values.unset}
267+ />
268+ )}
264269
265− {txType === 'L2' ? (
266− <Grid.Item
267− as={Button}
268− full
269− className="set-proxy"
270− center
271− solid
272− disabled={values.unset}
273− onClick={setProxy}>
274− {'Sign Transaction'}
275− </Grid.Item>
276− ) : (
277− <Grid.Item
278− full
279− as={InlineEthereumTransaction}
280− {...bind}
281− onReturn={() => pop()}
282− />
270+ <Grid.Item full as={FormError} />
271+
272+ {txType === 'L2' ? (
273+ <Grid.Item
274+ as={Button}
275+ full
276+ className="set-proxy"
277+ center
278+ solid
279+ disabled={values.unset}
280+ onClick={setProxy}>
281+ {'Sign Transaction'}
282+ </Grid.Item>
283+ ) : (
284+ <Grid.Item
285+ full
286+ as={InlineEthereumTransaction}
287+ {...bind}
288+ onReturn={() => pop()}
289+ />
290+ )}
291+ </>
283292 )}
284− </>
285− )}
286− </BridgeForm>
287− </Grid>
293+ </BridgeForm>
294+ </Grid>
295+ </BodyPane>
296+ </Window>
288297 );
289298}
src/views/UrbitID/Transfer.js+78−68
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
22import cn from 'classnames';
33import { Grid, Text, Button } from 'indigo-react';
44import * as azimuth from 'azimuth-js';
5+import { Row } from '@tlon/indigo-react';
56
67import { useNetwork } from 'store/network';
78import { usePointCursor } from 'store/pointCursor';
@@ -16,13 +17,15 @@ import useEthereumTransaction from 'lib/useEthereumTransaction';
1617import { GAS_LIMITS } from 'lib/constants';
1718import useRoller from 'lib/useRoller';
1819
19−import ViewHeader from 'components/ViewHeader';
2020import NoticeBox from 'components/NoticeBox';
2121import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2222import { AddressInput } from 'form/Inputs';
2323import { composeValidator, buildAddressValidator } from 'form/validators';
2424import BridgeForm from 'form/BridgeForm';
2525import FormError from 'form/FormError';
26+import Window from 'components/L2/Window/Window';
27+import HeaderPane from 'components/L2/Window/HeaderPane';
28+import BodyPane from 'components/L2/Window/BodyPane';
2629
2730function useTransfer() {
2831 const { contracts } = useNetwork();
@@ -102,75 +105,82 @@ export default function AdminTransfer() {
102105 );
103106
104107 return (
105− <Grid gap={1}>
106− <Grid.Item full as={ViewHeader}>
107− Transfer Point
108− </Grid.Item>
109−
110− <BridgeForm validate={validate} onValues={onValues}>
111− {({ handleSubmit, values }) => (
112− <>
113− {!completed && (
114− <Grid.Item full as={Text} className={cn('f5 wrap')}>
115− Transfer {name} to a new owner.
116− </Grid.Item>
117− )}
118− {!completed && needLockupWarning && (
119− <Grid.Item full as={NoticeBox}>
120− You have stars in lockup. These will not transfer with the{' '}
121− galaxy. If you wish, you must transfer the lockup separately.
122− </Grid.Item>
123− )}
124− {completed && (
108+ <Window>
109+ <HeaderPane>
110+ <Row className="header-row">
111+ <h5>Transfer Point</h5>
112+ </Row>
113+ </HeaderPane>
114+ <BodyPane>
115+ <Grid style={{ width: '100%' }} full gap={1}>
116+ <BridgeForm validate={validate} onValues={onValues}>
117+ {({ handleSubmit, values }) => (
125118 <>
126− <Grid.Item full as={Text} className="wrap">
127− Started transferring <span className="mono">{name}</span> to{' '}
128− </Grid.Item>
129− <Grid.Item full as={Text}>
130− <span className="mono gray4">{values.address}</span>
131− </Grid.Item>
132− <Grid.Item full as={Text} className="mt6">
133− Until they accept the transfer, you still own{' '}
134− <span className="mono">{name}</span>
135− </Grid.Item>
119+ {!completed && (
120+ <Grid.Item full as={Text} className={cn('f5 wrap')}>
121+ Transfer {name} to a new owner.
122+ </Grid.Item>
123+ )}
124+ {!completed && needLockupWarning && (
125+ <Grid.Item full as={NoticeBox}>
126+ You have stars in lockup. These will not transfer with the{' '}
127+ galaxy. If you wish, you must transfer the lockup
128+ separately.
129+ </Grid.Item>
130+ )}
131+ {completed && (
132+ <>
133+ <Grid.Item full as={Text} className="wrap">
134+ Started transferring <span className="mono">{name}</span>
135+ to{' '}
136+ </Grid.Item>
137+ <Grid.Item full as={Text}>
138+ <span className="mono gray4">{values.address}</span>
139+ </Grid.Item>
140+ <Grid.Item full as={Text} className="mt6">
141+ Until they accept the transfer, you still own{' '}
142+ <span className="mono">{name}</span>
143+ </Grid.Item>
144+ </>
145+ )}
146+
147+ {!completed && (
148+ <Grid.Item
149+ full
150+ as={AddressInput}
151+ className="mv4"
152+ name="address"
153+ label="Ethereum Address"
154+ disabled={inputsLocked}
155+ />
156+ )}
157+
158+ <Grid.Item full as={FormError} />
159+
160+ {currentL2 ? (
161+ <Grid.Item
162+ as={Button}
163+ full
164+ className="set-proxy"
165+ center
166+ solid
167+ disabled={values.unset}
168+ onClick={onSignTx}>
169+ {'Sign Transaction'}
170+ </Grid.Item>
171+ ) : (
172+ <Grid.Item
173+ full
174+ as={InlineEthereumTransaction}
175+ {...bind}
176+ onReturn={() => pop()}
177+ />
178+ )}
136179 </>
137180 )}
138−
139− {!completed && (
140− <Grid.Item
141− full
142− as={AddressInput}
143− className="mv4"
144− name="address"
145− label="Ethereum Address"
146− disabled={inputsLocked}
147− />
148− )}
149−
150− <Grid.Item full as={FormError} />
151−
152− {currentL2 ? (
153− <Grid.Item
154− as={Button}
155− full
156− className="set-proxy"
157− center
158− solid
159− disabled={values.unset}
160− onClick={onSignTx}>
161− {'Sign Transaction'}
162− </Grid.Item>
163− ) : (
164− <Grid.Item
165− full
166− as={InlineEthereumTransaction}
167− {...bind}
168− onReturn={() => pop()}
169− />
170− )}
171− </>
172− )}
173− </BridgeForm>
174− </Grid>
181+ </BridgeForm>
182+ </Grid>
183+ </BodyPane>
184+ </Window>
175185 );
176186}
src/views/UrbitOS/ChangeSponsor.js+35−26
@@ -23,6 +23,10 @@ import { validateMinimumPatpByteLength } from 'lib/validators';
2323import { usePointCursor } from 'store/pointCursor';
2424import useEthereumTransaction from 'lib/useEthereumTransaction';
2525import { isStar } from 'lib/utils/point';
26+import Window from 'components/L2/Window/Window';
27+import HeaderPane from 'components/L2/Window/HeaderPane';
28+import { Box, Row } from '@tlon/indigo-react';
29+import BodyPane from 'components/L2/Window/BodyPane';
2630
2731function useChangeSponsor() {
2832 const { contracts } = useNetwork();
@@ -96,32 +100,37 @@ function ChangeSponsor({ onDone }) {
96100 [construct, unconstruct]
97101 );
98102 return (
99− <Grid>
100− <Grid.Item full as={ViewHeader}>
101− Change Sponsor
102− </Grid.Item>
103− <BridgeForm validate={validate} onValues={onValues}>
104− {() => (
105− <>
106− <Grid.Item
107− full
108− as={PointInput}
109− name="sponsor"
110− disabled={inputsLocked}
111− label="New sponsor"
112− />
113− <Grid.Item full as={FormError} />
114− <Grid.Item
115− full
116− as={InlineEthereumTransaction}
117− {...bind}
118− onReturn={onDone}
119− label="Request"
120− />
121− </>
122− )}
123− </BridgeForm>
124− </Grid>
103+ <Window>
104+ <HeaderPane>
105+ <Row className="header-row">
106+ <h5>Change Sponsor</h5>
107+ </Row>
108+ </HeaderPane>
109+ <BodyPane>
110+ <BridgeForm validate={validate} onValues={onValues}>
111+ {() => (
112+ <Box width="100%">
113+ <Grid.Item
114+ full
115+ as={PointInput}
116+ name="sponsor"
117+ disabled={inputsLocked}
118+ label="New sponsor"
119+ className="mv4"
120+ />
121+ <Grid.Item full as={FormError} />
122+ <Grid.Item
123+ full
124+ as={InlineEthereumTransaction}
125+ {...bind}
126+ onReturn={onDone}
127+ label="Request"
128+ />
129+ </Box>
130+ )}
131+ </BridgeForm>
132+ </BodyPane>
133+ </Window>
125134 );
126135}
127136
src/views/UrbitOS/Home.tsx+11−4
@@ -16,12 +16,12 @@ import { ReactComponent as KeyfileIcon } from 'assets/keyfile.svg';
1616
1717import { useLocalRouter } from 'lib/LocalRouter';
1818import { L1Point } from 'lib/types/L1Point';
19−import AlertBox from 'components/AlertBox';
2019import useKeyfileGenerator from 'lib/useKeyfileGenerator';
2120import { Box, Button, Row } from '@tlon/indigo-react';
2221import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
2322
2423import './UrbitOS.scss';
24+import Modal from 'components/L2/Modal';
2525
2626export default function UrbitOSHome() {
2727 const { pointCursor }: any = usePointCursor();
@@ -109,14 +109,12 @@ export default function UrbitOSHome() {
109109 </Button>
110110 {hasSetNetworkingKeys && (
111111 <Button className="secondary" onClick={toggleShowKeys}>
112− {showKeys ? 'Hide' : 'View'}
112+ View
113113 </Button>
114114 )}
115115 </Row>
116116 </Row>
117117
118− {showKeys && <Grid.Item full as={NetworkingKeys} point={point} />}
119−
120118 {!!code && (
121119 <Row className="between-row management">
122120 <Box>
@@ -129,6 +127,15 @@ export default function UrbitOSHome() {
129127 </Row>
130128 )}
131129 </BodyPane>
130+
131+ <Modal show={showKeys} hide={toggleShowKeys}>
132+ <Box className="show-keys-modal-content">
133+ <Box className="close" onClick={toggleShowKeys}>
134+ &#215;
135+ </Box>
136+ <Grid.Item full as={NetworkingKeys} point={point} />
137+ </Box>
138+ </Modal>
132139 </Window>
133140 );
134141}
src/views/UrbitOS/NetworkingKeys.js+19−11
@@ -23,7 +23,6 @@ import { GAS_LIMITS } from 'lib/constants';
2323import { addHexPrefix } from 'lib/utils/address';
2424import useKeyfileGenerator from 'lib/useKeyfileGenerator';
2525
26−import ViewHeader from 'components/ViewHeader';
2726import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
2827import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2928import NoticeBox from 'components/NoticeBox';
@@ -39,6 +38,10 @@ import BridgeForm from 'form/BridgeForm';
3938import Condition from 'form/Condition';
4039import FormError from 'form/FormError';
4140import { convertToInt } from 'lib/convertToInt';
41+import Window from 'components/L2/Window/Window';
42+import HeaderPane from 'components/L2/Window/HeaderPane';
43+import BodyPane from 'components/L2/Window/BodyPane';
44+import { Box, Row } from '@tlon/indigo-react';
4245
4346function useSetKeys(manualNetworkSeed, setManualNetworkSeed) {
4447 const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
@@ -255,13 +258,15 @@ export default function UrbitOSNetworkingKeys({
255258 'You need this keyfile to authenticate with your OS. Please Download.';
256259
257260 return (
258− <>
259− <Grid>
260− <Grid.Item full as={ViewHeader}>
261− {viewTitle}
262− </Grid.Item>
261+ <Window>
262+ <HeaderPane>
263+ <Row className="header-row">
264+ <h5>{viewTitle}</h5>
265+ </Row>
266+ </HeaderPane>
267+ <BodyPane>
263268 {!completed && (
264− <Grid.Item className="gray4 f5" full>
269+ <Grid.Item className="gray4 f5 mv2 align-self-start" full>
265270 Network Keys authenticate Urbit ID with Urbit OS.
266271 </Grid.Item>
267272 )}
@@ -272,6 +277,7 @@ export default function UrbitOSNetworkingKeys({
272277 className="mb3"
273278 as={DownloadKeyfileButton}
274279 solid
280+ left
275281 {...keyfileBind}
276282 />
277283 <Grid.Item full as={AlertBox} className="mb8">
@@ -284,7 +290,7 @@ export default function UrbitOSNetworkingKeys({
284290 onValues={onValues}
285291 initialValues={initialValues}>
286292 {() => (
287− <>
293+ <Box width="100%">
288294 {!completed && (
289295 <>
290296 <Grid.Item
@@ -307,6 +313,7 @@ export default function UrbitOSNetworkingKeys({
307313 name="networkSeed"
308314 label="Network Seed (32 bytes)"
309315 disabled={inputsLocked}
316+ className="mb6"
310317 />
311318 </Condition>
312319 <Grid.Item
@@ -315,6 +322,7 @@ export default function UrbitOSNetworkingKeys({
315322 name="useDiscontinuity"
316323 label="Breach Continuity"
317324 disabled={inputsLocked}
325+ className="mb6"
318326 />
319327
320328 <Grid.Item full as={FormError} />
@@ -340,10 +348,10 @@ export default function UrbitOSNetworkingKeys({
340348 onReturn={() => pop()}
341349 />
342350 )}
343− </>
351+ </Box>
344352 )}
345353 </BridgeForm>
346− </Grid>
347− </>
354+ </BodyPane>
355+ </Window>
348356 );
349357}
src/views/UrbitOS/UrbitOS.scss+10
@@ -1,5 +1,15 @@
1+@import 'style/shared/variables.scss';
2+
13.os-home {
24 .keyfile > svg {
35 margin-right: 4px;
46 }
57}
8+
9+.show-keys-modal-content {
10+ width: 375px;
11+ background-color: white;
12+ padding: 1rem 1.5rem;
13+ border-radius: $large-border-radius;
14+ position: relative;
15+}