on master help sign in
UI and UX updates for bridge
3ca083dWill Galebach
package-lock.json+15−2
@@ -11954,6 +11954,11 @@
11954 "randomfill": "^1.0.3"
11954 "randomfill": "^1.0.3"
11955 }
11955 }
11956 },
11956 },
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+ },
11957 "crypto-random-string": {
11962 "crypto-random-string": {
11958 "version": "1.0.0",
11963 "version": "1.0.0",
11959 "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
11964 "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
@@ -22480,8 +22485,7 @@
22480 "lz-string": {
22485 "lz-string": {
22481 "version": "1.4.4",
22486 "version": "1.4.4",
22482 "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
22487 "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz",
22483− "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
22488+ "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY="
22484− "dev": true
22485 },
22489 },
22486 "magic-string": {
22490 "magic-string": {
22487 "version": "0.25.7",
22491 "version": "0.25.7",
@@ -29604,6 +29608,15 @@
29604 "node-gyp-build": "^4.2.0"
29608 "node-gyp-build": "^4.2.0"
29605 }
29609 }
29606 },
29610 },
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+ },
29607 "seedrandom": {
29620 "seedrandom": {
29608 "version": "3.0.5",
29621 "version": "3.0.5",
29609 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
29622 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
package.json+1
@@ -74,6 +74,7 @@
74 "react-teleporter": "^2.2.0",
74 "react-teleporter": "^2.2.0",
75 "react-transition-group": "^4.4.2",
75 "react-transition-group": "^4.4.2",
76 "secp256k1": "^4.0.2",
76 "secp256k1": "^4.0.2",
77+ "secure-ls": "^1.2.6",
77 "styled-components": "^5.3.0",
78 "styled-components": "^5.3.0",
78 "styled-system": "^5.1.5",
79 "styled-system": "^5.1.5",
79 "trezor-connect": "^8.1.29",
80 "trezor-connect": "^8.1.29",
src/components/InlineEthereumTransaction.js−8
@@ -223,14 +223,6 @@ export default function InlineEthereumTransaction({
223 {...needFunds}
223 {...needFunds}
224 />
224 />
225 )}
225 )}
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− )} */}
234 </>
226 </>
235 )}
227 )}
236 </BridgeForm>
228 </BridgeForm>
src/components/L2/Dropdowns/AccountsDropdown.scss+1−1
@@ -4,7 +4,7 @@
4 font-size: 14px;
4 font-size: 14px;
5 position: relative;
5 position: relative;
6
6
7− .selector {
7+ .selector, .dropdown-content .entry.point {
8 font-family: "Source Code Pro", "Roboto mono", "Courier New", monospace;
8 font-family: "Source Code Pro", "Roboto mono", "Courier New", monospace;
9 }
9 }
10
10
src/components/L2/Dropdowns/AccountsDropdown.tsx+7−5
@@ -31,7 +31,7 @@ interface AccountsDropdownProps {
31const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
31const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
32 const [open, setOpen] = useState<boolean>(false);
32 const [open, setOpen] = useState<boolean>(false);
33 const walletInfo: any = useWallet();
33 const walletInfo: any = useWallet();
34− const { push, names, reset }: any = useHistory();
34+ const { push, popTo, names, reset }: any = useHistory();
35 const { setPointCursor }: any = usePointCursor();
35 const { setPointCursor }: any = usePointCursor();
36 const { controlledPoints }: any = usePointCache();
36 const { controlledPoints }: any = usePointCache();
37 const { currentL2 }: any = useRollerStore();
37 const { currentL2 }: any = useRollerStore();
@@ -61,6 +61,8 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
61 [push, names.POINT, setOpen, setPointCursor]
61 [push, names.POINT, setOpen, setPointCursor]
62 );
62 );
63
63
64+ const goHome = useCallback(() => popTo(names.POINTS), [names, popTo]);
65+
64 const goMigrate = useCallback(() => push(names.MIGRATE_L2), [
66 const goMigrate = useCallback(() => push(names.MIGRATE_L2), [
65 names.MIGRATE_L2,
67 names.MIGRATE_L2,
66 push,
68 push,
@@ -81,7 +83,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
81 reset();
83 reset();
82 };
84 };
83
85
84− const { canSpawn, isParent } = useCurrentPermissions();
86+ const { canSpawn, isParent, isStar } = useCurrentPermissions();
85
87
86 return (
88 return (
87 <Dropdown
89 <Dropdown
@@ -93,7 +95,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
93 className="current-address"
95 className="current-address"
94 position={TooltipPosition.Left}
96 position={TooltipPosition.Left}
95 text={currentAddress}>
97 text={currentAddress}>
96− {displayAddress}
98+ <Box onClick={goHome}>{displayAddress}</Box>
97 </CopiableAddressWrap>
99 </CopiableAddressWrap>
98 <Box className="divider" />
100 <Box className="divider" />
99 <Box className="points">
101 <Box className="points">
@@ -102,7 +104,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
102
104
103 return (
105 return (
104 <Row
106 <Row
105− className="entry"
107+ className="entry point"
106 onClick={selectPoint(point)}
108 onClick={selectPoint(point)}
107 key={`point-${point}`}>
109 key={`point-${point}`}>
108 <Box>{patp}</Box>
110 <Box>{patp}</Box>
@@ -135,7 +137,7 @@ const AccountsDropdown = ({ showMigrate = false }: AccountsDropdownProps) => {
135 )}
137 )}
136 {isParent && canSpawn && (
138 {isParent && canSpawn && (
137 <Row className="entry" onClick={goIssuePoint}>
139 <Row className="entry" onClick={goIssuePoint}>
138− <Box>Issue Point</Box>
140+ <Box>Spawn{isStar ? ' Planets' : ' Stars'}</Box>
139 <Icon icon="Plus" />
141 <Icon icon="Plus" />
140 </Row>
142 </Row>
141 )}
143 )}
src/components/L2/Dropdowns/FeeDropdown.tsx+1−1
@@ -78,7 +78,7 @@ export default function FeeDropdown({
78 <Row
78 <Row
79 className="price"
79 className="price"
80 onClick={selectPrice(value)}
80 onClick={selectPrice(value)}
81− key={value.wait}>
81+ key={`gas-price-${ind}`}>
82 {PRICE_LABELS[ind]}: {formatDisplay(value)}
82 {PRICE_LABELS[ind]}: {formatDisplay(value)}
83 </Row>
83 </Row>
84 )
84 )
src/components/L2/Headers/L2BackHeader.tsx+1−1
@@ -17,7 +17,7 @@ import L2BackButton from './L2BackButton';
17export interface L2BackHeaderProps {
17export interface L2BackHeaderProps {
18 back?: () => void;
18 back?: () => void;
19 hideBalance?: boolean;
19 hideBalance?: boolean;
20− className: string;
20+ className?: string;
21}
21}
22
22
23const L2BackHeader = ({
23const L2BackHeader = ({
src/components/L2/Headers/L2PointHeader.tsx+3−2
@@ -47,13 +47,14 @@ const L2PointHeader = ({
47 push,
47 push,
48 ]);
48 ]);
49
49
50+ const showInvites =
50− const star = pointCursor?.value && isStar(pointCursor.value);
51+ !hideInvites && Boolean(pointCursor?.value && isStar(pointCursor.value));
51
52
52 return (
53 return (
53 <Row className="l2-point-header">
54 <Row className="l2-point-header">
54 <AccountsDropdown showMigrate={showMigrate} />
55 <AccountsDropdown showMigrate={showMigrate} />
55 <Row className="info">
56 <Row className="info">
56− {!hideInvites && star && (
57+ {showInvites && (
57 <Row onClick={goToInvites} className="invites">
58 <Row onClick={goToInvites} className="invites">
58 <InviteIcon />
59 <InviteIcon />
59 {numInvites}
60 {numInvites}
src/components/L2/LoadingOverlay.scss+21−3
@@ -1,7 +1,7 @@
1@import 'style/shared/variables.scss';
1@import 'style/shared/variables.scss';
2
2
3.loading-overlay {
3.loading-overlay {
4− background-color: rgba(0, 0, 0, 0);
4+ background-color: rgba(0, 0, 0, 0.3);
5 position: fixed;
5 position: fixed;
6 top: 0;
6 top: 0;
7 bottom: 0;
7 bottom: 0;
@@ -13,9 +13,27 @@
13 align-items: center;
13 align-items: center;
14 z-index: 1000;
14 z-index: 1000;
15
15
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+
16 svg {
36 svg {
17− display: none;
18− margin-bottom: 120px;
19 height: 60px;
37 height: 60px;
20 width: 60px;
38 width: 60px;
21 }
39 }
src/components/L2/LoadingOverlay.tsx+11−2
@@ -2,14 +2,23 @@ import { Box, LoadingSpinner } from '@tlon/indigo-react';
2
2
3import './LoadingOverlay.scss';
3import './LoadingOverlay.scss';
4
4
5−const LoadingOverlay = ({ loading = false }: { loading: boolean }) => {
5+const LoadingOverlay = ({
6+ loading = false,
7+ text,
8+}: {
9+ loading: boolean;
10+ text?: string;
11+}) => {
6 if (!loading) {
12 if (!loading) {
7 return null;
13 return null;
8 }
14 }
9
15
10 return (
16 return (
11 <Box className="loading-overlay">
17 <Box className="loading-overlay">
18+ <Box className={`${text ? 'solid' : ''}`}>
19+ {text && <Box className="loader-text">{text}</Box>}
12− <LoadingSpinner foreground="white" background="rgba(0,0,0,0.3)" />
20+ <LoadingSpinner foreground="white" background="rgba(0,0,0,0.3)" />
21+ </Box>
13 </Box>
22 </Box>
14 );
23 );
15};
24};
src/components/NetworkingKeys.js+34−27
@@ -45,7 +45,7 @@ export default function NetworkingKeys({ point }) {
45 ),
45 ),
46 Just: ({ value: key }) => (
46 Just: ({ value: key }) => (
47 <>
47 <>
48− <Grid.Item full as="code" className="f5 mono wrap">
48+ <Grid.Item full as="div" className="f5 mono">
49 0x
49 0x
50 </Grid.Item>
50 </Grid.Item>
51 <Grid.Item full as="code" className="f5 mono wrap">
51 <Grid.Item full as="code" className="f5 mono wrap">
@@ -58,59 +58,66 @@ export default function NetworkingKeys({ point }) {
58 );
58 );
59
59
60 const renderDetail = (title, value) => (
60 const renderDetail = (title, value) => (
61− <Flex.Item as={Flex} col>
61+ <Flex.Item as={Flex} row>
62− <Flex.Item as={H5} className="gray4">
62+ <Flex.Item style={{ minWidth: 192, marginBottom: 8 }} as={H5}>
63 {title}
63 {title}
64 </Flex.Item>
64 </Flex.Item>
65 {value.matchWith({
65 {value.matchWith({
66 Nothing: () => (
66 Nothing: () => (
67− <Flex.Item as={Text} className="f5 gray4">
67+ <Flex.Item as={H5} className="f5 gray4">
68 Unset
68 Unset
69 </Flex.Item>
69 </Flex.Item>
70 ),
70 ),
71− Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
71+ Just: ({ value }) => <Flex.Item as={H5}>{value}</Flex.Item>,
72 })}
72 })}
73 </Flex.Item>
73 </Flex.Item>
74 );
74 );
75
75
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+
76 return (
97 return (
77 <>
98 <>
99+ <h5 className="fw-bold">Network Keys</h5>
100+ <h5 className="gray4">
101+ Revision {revisionNumber} {revisionTime}
102+ </h5>
78 {renderNetworkKeySection(
103 {renderNetworkKeySection(
79− 'Authentication',
104+ 'Authentication Key',
80 details.chain(chainKeyProp('authenticationKey'))
105 details.chain(chainKeyProp('authenticationKey'))
81 )}
106 )}
82 {renderNetworkKeySection(
107 {renderNetworkKeySection(
83− 'Encryption',
108+ 'Encryption Key',
84 details.chain(chainKeyProp('encryptionKey'))
109 details.chain(chainKeyProp('encryptionKey'))
85 )}
110 )}
86− <Grid.Item full as={Flex} row justify="between" className="mt3">
111+ <Grid.Item full as={Flex} col justify="between" className="mt3">
87− {renderDetail(
88− 'Revision',
89− details.map(d => d.keyRevisionNumber)
90− )}
91 {renderDetail(
112 {renderDetail(
92 'Continuity Era',
113 'Continuity Era',
93 details.map(d => d.continuityNumber)
114 details.map(d => d.continuityNumber)
94 )}
115 )}
95 {renderDetail(
116 {renderDetail(
96− 'Crypto Suite Ver.',
117+ 'Crypto Suite Version',
97 details.map(d => d.cryptoSuiteVersion)
118 details.map(d => d.cryptoSuiteVersion)
98 )}
119 )}
99 </Grid.Item>
120 </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− )}
114 </>
121 </>
115 );
122 );
116}
123}
src/lib/constants.ts+2
@@ -149,6 +149,7 @@ const TRANSACTION_TYPE_TITLES = {
149 'transfer-point': 'Point Transferred',
149 'transfer-point': 'Point Transferred',
150 'set-management-proxy': 'Management Key Changed',
150 'set-management-proxy': 'Management Key Changed',
151};
151};
152+const DUMMY_L2_ADDRESS = '0x1111111111111111111111111111111111111111';
152
153
153export {
154export {
154 CHECK_BLOCK_EVERY_MS,
155 CHECK_BLOCK_EVERY_MS,
@@ -188,4 +189,5 @@ export {
188 TRANSACTION_STATUS_ICONS,
189 TRANSACTION_STATUS_ICONS,
189 TRANSACTION_TYPE_ICONS,
190 TRANSACTION_TYPE_ICONS,
190 TRANSACTION_TYPE_TITLES,
191 TRANSACTION_TYPE_TITLES,
192+ DUMMY_L2_ADDRESS,
191};
193};
src/lib/types/Invite.ts+4
@@ -4,3 +4,7 @@ export interface Invite {
4 hash: string;
4 hash: string;
5 owner: string;
5 owner: string;
6}
6}
7+
8+export interface Invites {
9+ [key: number]: Invite;
10+}
src/lib/useRoller.ts+70−63
@@ -1,14 +1,13 @@
1import { useCallback, useEffect, useMemo, useState } from 'react';
1import { useCallback, useEffect, useMemo, useState } from 'react';
2−import _ from 'lodash';
3import * as azimuth from 'azimuth-js';
2import * as azimuth from 'azimuth-js';
4import * as need from 'lib/need';
3import * as need from 'lib/need';
5−import { Just } from 'folktale/maybe';
4+import SecureLS from 'secure-ls';
6
5
7import { Invite } from 'lib/types/Invite';
6import { Invite } from 'lib/types/Invite';
8import { convertToInt } from './convertToInt';
7import { convertToInt } from './convertToInt';
9import { isDevelopment, isRopsten } from './flags';
8import { isDevelopment, isRopsten } from './flags';
10−import { hasInvite, generateInviteWallet } from './utils/roller';
9+import { generateInviteWallet, getPendingSpawns } from './utils/roller';
11−import { INVITES_PER_PAGE, ROLLER_HOSTS } from './constants';
10+import { ROLLER_HOSTS } from './constants';
12
11
13import { useNetwork } from 'store/network';
12import { useNetwork } from 'store/network';
14import { usePointCursor } from 'store/pointCursor';
13import { usePointCursor } from 'store/pointCursor';
@@ -38,8 +37,6 @@ import {
38 Options,
37 Options,
39 EthAddress,
38 EthAddress,
40 UnspawnedPoints,
39 UnspawnedPoints,
41− SpawnParams,
42− L2Data,
43} from '@urbit/roller-api';
40} from '@urbit/roller-api';
44
41
45import {
42import {
@@ -50,13 +47,7 @@ import {
50 registerProxyAddress,
47 registerProxyAddress,
51} from './utils/roller';
48} from './utils/roller';
52
49
53−function isSpawn(tx: L2Data | undefined): tx is SpawnParams {
50+const ONE_SECOND = 1000;
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−}
60
51
61const inviteTemplate = (
52const inviteTemplate = (
62 planet: number,
53 planet: number,
@@ -82,6 +73,7 @@ export default function useRoller() {
82 nextBatchTime,
73 nextBatchTime,
83 currentL2,
74 currentL2,
84 invitePoints,
75 invitePoints,
76+ invites,
85 setNextBatchTime,
77 setNextBatchTime,
86 setNextRoll,
78 setNextRoll,
87 setPendingTransactions,
79 setPendingTransactions,
@@ -131,6 +123,11 @@ export default function useRoller() {
131 });
123 });
132 }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
124 }, [api]); // eslint-disable-line react-hooks/exhaustive-deps
133
125
126+ const ls = new SecureLS({
127+ isCompression: false,
128+ encryptionSecret: authToken.getOrElse('default'),
129+ });
130+
134 const generateInviteCodes = useCallback(
131 const generateInviteCodes = useCallback(
135 async (numInvites: number) => {
132 async (numInvites: number) => {
136 const _point = need.point(pointCursor);
133 const _point = need.point(pointCursor);
@@ -154,25 +151,12 @@ export default function useRoller() {
154
151
155 const pendingTxs = await api.getPendingByShip(_point);
152 const pendingTxs = await api.getPendingByShip(_point);
156
153
157− const pendingSpawns = _.reduce(
154+ const pendingSpawns = getPendingSpawns(pendingTxs);
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− );
167
155
168 planets = planets.filter((point: number) => !pendingSpawns.has(point));
156 planets = planets.filter((point: number) => !pendingSpawns.has(point));
169
157
170 const starInfo = await api.getPoint(_point);
158 const starInfo = await api.getPoint(_point);
171− const tickets: {
159+ const invites: Invite[] = [];
172− ticket: string;
173− planet: number;
174− owner: string;
175− }[] = [];
176 const requests: Promise<string>[] = [];
160 const requests: Promise<string>[] = [];
177
161
178 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
162 const proxy = getSpawnProxy(starInfo.ownership!, _wallet.address);
@@ -244,12 +228,15 @@ export default function useRoller() {
244 );
228 );
245 await Promise.all(requests);
229 await Promise.all(requests);
246
230
247− tickets.push({
231+ invites.push({
232+ hash: '',
248 ticket,
233 ticket,
249 planet,
234 planet,
250 owner: inviteWallet.ownership.keys.address,
235 owner: inviteWallet.ownership.keys.address,
251 });
236 });
252 }
237 }
238+
239+ setStoredInvites(ls, invites);
253 },
240 },
254 [
241 [
255 api,
242 api,
@@ -259,7 +246,7 @@ export default function useRoller() {
259 wallet,
246 wallet,
260 web3,
247 web3,
261 getDetails,
248 getDetails,
262− authMnemonic,
249+ ls,
263 setInviteGeneratingNum,
250 setInviteGeneratingNum,
264 ]
251 ]
265 );
252 );
@@ -290,23 +277,25 @@ export default function useRoller() {
290 const newPending = await api.getPendingByShip(curPoint);
277 const newPending = await api.getPendingByShip(curPoint);
291
278
292 setPendingTransactions(newPending);
279 setPendingTransactions(newPending);
293−
294− // const allTransactions = await api.getHistory()
295 } catch (error) {
280 } catch (error) {
296 console.warn('ERROR GETTING PENDING', error);
281 console.warn('ERROR GETTING PENDING', error);
297 }
282 }
298 }, [api, setPendingTransactions, pointCursor]);
283 }, [api, setPendingTransactions, pointCursor]);
299
284
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+
300 const getInvites = useCallback(
294 const getInvites = useCallback(
301− async (isL2: boolean, page: number, getAll = false) => {
295+ async (isL2: boolean, getAll = false) => {
302 try {
296 try {
303 const curPoint: number = Number(need.point(pointCursor));
297 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)));
298+ setInvites(invitePoints.map(p => inviteTemplate(p)));
309− }
310
299
311 const _authToken = authToken.getOrElse(null);
300 const _authToken = authToken.getOrElse(null);
312 const _contracts = contracts.getOrElse(null);
301 const _contracts = contracts.getOrElse(null);
@@ -332,38 +321,27 @@ export default function useRoller() {
332
321
333 const newInvites: Invite[] = [];
322 const newInvites: Invite[] = [];
334 // Iterate over all of the stored invites, generating wallet info as necessary
323 // Iterate over all of the stored invites, generating wallet info as necessary
324+ const storedInvites = getStoredInvites(ls);
325+
335 for (let i = 0; i < invitePlanets.length; i++) {
326 for (let i = 0; i < invitePlanets.length; i++) {
336 setInviteGeneratingNum(i + 1);
327 setInviteGeneratingNum(i + 1);
337 const planet = invitePlanets[i];
328 const planet = invitePlanets[i];
338− const invite = storedInvites.find(hasInvite(planet));
329+ const invite = storedInvites[planet];
339−
340− const start = page * INVITES_PER_PAGE;
341− const end = start + INVITES_PER_PAGE;
342
330
343− if (invite) {
331+ if (invite?.ticket) {
344 newInvites.push(invite);
332 newInvites.push(invite);
345− } else if (getAll || (i >= start && i < end)) {
333+ } else if (getAll) {
346 if (isDevelopment) {
334 if (isDevelopment) {
347 console.log('MISSING INVITE INFO', planet);
335 console.log('MISSING INVITE INFO', planet);
348 }
336 }
349− const { ticket, inviteWallet } = await generateInviteWallet(
337+ const newInvite = await generateInviteInfo(planet, _authToken);
350− planet,
351− _authToken
352− );
353−
354− newInvites.push(
338+ newInvites.push(newInvite);
355− inviteTemplate(
356− planet,
357− ticket,
358− inviteWallet.ownership.keys.address
359− )
360− );
361 } else {
339 } else {
362 newInvites.push(inviteTemplate(planet));
340 newInvites.push(inviteTemplate(planet));
363 }
341 }
364 }
342 }
365
343
366− setStoredInvites(curPoint, newInvites);
344+ setStoredInvites(ls, newInvites);
367 setInvites(newInvites);
345 setInvites(newInvites);
368
346
369 return newInvites;
347 return newInvites;
@@ -380,11 +358,28 @@ export default function useRoller() {
380 getDetails,
358 getDetails,
381 pointCursor,
359 pointCursor,
382 invitePoints,
360 invitePoints,
361+ ls,
383 setInvites,
362 setInvites,
384 setInviteGeneratingNum,
363 setInviteGeneratingNum,
385 ]
364 ]
386 );
365 );
387
366
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+
388 const getNumInvites = useCallback(
383 const getNumInvites = useCallback(
389 async (isL2: boolean) => {
384 async (isL2: boolean) => {
390 try {
385 try {
@@ -400,12 +395,22 @@ export default function useRoller() {
400 curPoint
395 curPoint
401 );
396 );
402
397
398+ const newPending = await api.getPendingByShip(curPoint);
399+ const pendingSpawns = getPendingSpawns(newPending);
400+
403 const outgoingPoints = getOutgoingPoints(
401 const outgoingPoints = getOutgoingPoints(
404 controlledPoints,
402 controlledPoints,
405 getDetails
403 getDetails
406 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
404 ).filter((p: number) => isPlanet(p) && availablePoints.includes(p));
407
405
406+ console.log(pendingSpawns, outgoingPoints, invitePoints)
407+
408+ setPendingTransactions(newPending);
409+ setInvitePoints(
410+ invitePoints
408− setInvitePoints(invitePoints.concat(outgoingPoints));
411+ .concat(outgoingPoints)
412+ .filter(p => !pendingSpawns.has(p))
413+ );
409 }
414 }
410 } catch (e) {
415 } catch (e) {
411 if (isDevelopment) {
416 if (isDevelopment) {
@@ -416,11 +421,12 @@ export default function useRoller() {
416 [
421 [
417 api,
422 api,
418 pointCursor,
423 pointCursor,
419− setInvitePoints,
420 authToken,
424 authToken,
421 contracts,
425 contracts,
422 controlledPoints,
426 controlledPoints,
423 getDetails,
427 getDetails,
428+ setInvitePoints,
429+ setPendingTransactions,
424 ]
430 ]
425 );
431 );
426
432
@@ -645,12 +651,11 @@ export default function useRoller() {
645 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
651 const nextRoll = getTimeToNextBatch(nextBatchTime, new Date().getTime());
646 setNextRoll(nextRoll);
652 setNextRoll(nextRoll);
647
653
648− if (nextBatchTime <= new Date().getTime()) {
654+ if (nextBatchTime - ONE_SECOND <= new Date().getTime()) {
649 api.getRollerConfig().then(response => {
655 api.getRollerConfig().then(response => {
650 setNextBatchTime(response.nextBatch);
656 setNextBatchTime(response.nextBatch);
657+ getNumInvites(currentL2);
651 });
658 });
652−
653− getInvites(currentL2, 0);
654 }
659 }
655 }, 1000);
660 }, 1000);
656
661
@@ -669,5 +674,7 @@ export default function useRoller() {
669 setProxyAddress,
674 setProxyAddress,
670 configureNetworkingKeys,
675 configureNetworkingKeys,
671 getNumInvites,
676 getNumInvites,
677+ showInvite,
678+ ls,
672 };
679 };
673}
680}
src/lib/utils/invite.ts+13−1
@@ -2,7 +2,19 @@ import * as ob from 'urbit-ob';
2
2
3export const generateUrl = (ticket: string, planet: number) => {
3export const generateUrl = (ticket: string, planet: number) => {
4 if (ticket && planet) {
4 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)}`;
6 } else if (planet) {
18 } else if (planet) {
7 return ob.patp(planet);
19 return ob.patp(planet);
8 }
20 }
src/lib/utils/point.ts+3
@@ -1,5 +1,8 @@
1import { azimuth } from 'azimuth-js';
1import { azimuth } from 'azimuth-js';
2
2
3+export const isGalaxy = (point: number) =>
4+ azimuth.getPointSize(point) === azimuth.PointSize.Galaxy;
5+
3export const isStar = (point: number) =>
6export const isStar = (point: number) =>
4 azimuth.getPointSize(point) === azimuth.PointSize.Star;
7 azimuth.getPointSize(point) === azimuth.PointSize.Star;
5
8
src/lib/utils/roller.ts+19
@@ -10,6 +10,9 @@ import RollerRPCAPI, {
10 EthAddress,
10 EthAddress,
11 AddressParams,
11 AddressParams,
12 Ship,
12 Ship,
13+ SpawnParams,
14+ L2Data,
15+ PendingTransaction,
13} from '@urbit/roller-api';
16} from '@urbit/roller-api';
14
17
15import { deriveNetworkKeys, CRYPTO_SUITE_VERSION } from 'lib/keys';
18import { deriveNetworkKeys, CRYPTO_SUITE_VERSION } from 'lib/keys';
@@ -200,3 +203,19 @@ export const reticketL2Point = async () => {};
200
203
201export const hasInvite = (point: number) => (invite: Invite) =>
204export const hasInvite = (point: number) => (invite: Invite) =>
202 invite.planet === point && invite.ticket.length > 0;
205 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) {
84 }
84 }
85 case NETWORK_TYPES.ROPSTEN: {
85 case NETWORK_TYPES.ROPSTEN: {
86 const endpoint = `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
86 const endpoint = `https://ropsten.infura.io/v3/${process.env.REACT_APP_INFURA_ENDPOINT}`;
87+ console.log('ENDPOINT', endpoint)
87
88
88 initWeb3(
89 initWeb3(
89 new Web3.providers.HttpProvider(endpoint),
90 new Web3.providers.HttpProvider(endpoint),
src/store/storage/roller.ts+18−15
@@ -1,24 +1,27 @@
1+import SecureLS from 'secure-ls';
1−import { Invite } from 'lib/types/Invite';
2+import { Invite, Invites } from 'lib/types/Invite';
2
3
3const INVITES_KEY = 'invites';
4const INVITES_KEY = 'invites';
4const HIDE_MIGRATION_MESSAGE_KEY = 'hide_migration_message';
5const HIDE_MIGRATION_MESSAGE_KEY = 'hide_migration_message';
5
6
6−export const getStoredInvites = (point: number): Invite[] => {
7+export const getStoredInvites = (ls: SecureLS): Invites => {
7− const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
8+ let invites = {};
8− const invites = JSON.parse(invitesString);
9+ try {
9−
10+ invites = ls.get(INVITES_KEY);
10− if (invites[String(point)]) {
11+ } catch (e) {}
11− return invites[String(point)];
12− }
13−
14− return [];
12+ return invites;
15};
13};
16
14
17−export const setStoredInvites = (point: number, newInvites: Invite[]) => {
15+export const setStoredInvites = (ls: SecureLS, newInvites: Invite[]) => {
18− const invitesString = localStorage.getItem(INVITES_KEY) || '{}';
16+ let invites: Invites = {};
19− const invites = JSON.parse(invitesString);
17+ try {
18+ invites = ls.get(INVITES_KEY);
19+ } catch (e) {}
20+
21+ newInvites.forEach(invite => {
20− invites[Number(point)] = newInvites;
22+ invites[Number(invite.planet)] = invite;
23+ });
21− localStorage.setItem(INVITES_KEY, JSON.stringify(invites));
24+ ls.set(INVITES_KEY, invites);
22};
25};
23
26
24export const clearInvitesStorage = () => localStorage.removeItem(INVITES_KEY);
27export const clearInvitesStorage = () => localStorage.removeItem(INVITES_KEY);
src/views/Invite/Cohort.scss+6
@@ -77,6 +77,10 @@
77 border: 1px solid $light-gray;
77 border: 1px solid $light-gray;
78 width: 390px;
78 width: 390px;
79 padding: 3px 8px;
79 padding: 3px 8px;
80+
81+ &.shortened {
82+ width: 300px;
83+ }
80 }
84 }
81
85
82 .copy-invite, .ticket-loader {
86 .copy-invite, .ticket-loader {
@@ -111,6 +115,8 @@
111 .generate-button {
115 .generate-button {
112 margin-bottom: 20px;
116 margin-bottom: 20px;
113 border-radius: $medium-border-radius;
117 border-radius: $medium-border-radius;
118+ color: white;
119+ background-color: black;
114 }
120 }
115 }
121 }
116
122
src/views/Invite/Cohort.tsx+31−36
@@ -1,11 +1,12 @@
1import React, { useEffect, useCallback, useState } from 'react';
1import React, { useEffect, useCallback, useState } from 'react';
2import { Grid, Button } from 'indigo-react';
2import { Grid, Button } from 'indigo-react';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4+import { Just } from 'folktale/maybe';
4import {
5import {
5 Icon,
6 Icon,
6− LoadingSpinner,
7 Row,
7 Row,
8 StatelessTextInput,
8 StatelessTextInput,
9+ Button as Button2,
9} from '@tlon/indigo-react';
10} from '@tlon/indigo-react';
10
11
11import { usePointCursor } from 'store/pointCursor';
12import { usePointCursor } from 'store/pointCursor';
@@ -16,10 +17,10 @@ import { useRollerStore } from 'store/roller';
16
17
17import { useLocalRouter } from 'lib/LocalRouter';
18import { useLocalRouter } from 'lib/LocalRouter';
18import * as need from 'lib/need';
19import * as need from 'lib/need';
19−import * as wg from 'lib/walletgen';
20import useRoller from 'lib/useRoller';
20import useRoller from 'lib/useRoller';
21import {
21import {
22 generateUrl,
22 generateUrl,
23+ generateUrlAbbreviation,
23 generateCsvLine,
24 generateCsvLine,
24 generateCsvName,
25 generateCsvName,
25} from 'lib/utils/invite';
26} from 'lib/utils/invite';
@@ -44,6 +45,7 @@ import {
44 DEFAULT_CSV_NAME,
45 DEFAULT_CSV_NAME,
45} from 'lib/constants';
46} from 'lib/constants';
46import { isPlanet } from 'lib/utils/point';
47import { isPlanet } from 'lib/utils/point';
48+import { generateInviteWallet } from 'lib/utils/roller';
47
49
48interface L1Invite {
50interface L1Invite {
49 ticket: string;
51 ticket: string;
@@ -61,18 +63,19 @@ export default function InviteCohort() {
61 invitePoints,
63 invitePoints,
62 setInviteGeneratingNum,
64 setInviteGeneratingNum,
63 setInvites,
65 setInvites,
64− // inviteGeneratingNum,
65 } = useRollerStore();
66 } = useRollerStore();
66 const {
67 const {
68+ ls,
67 generateInviteCodes,
69 generateInviteCodes,
68 getPendingTransactions,
70 getPendingTransactions,
69 getInvites,
71 getInvites,
72+ showInvite,
70 } = useRoller();
73 } = useRoller();
71 const { pointCursor }: any = usePointCursor();
74 const { pointCursor }: any = usePointCursor();
72 const { contracts }: any = useNetwork();
75 const { contracts }: any = useNetwork();
73 const { syncControlledPoints }: any = usePointCache();
76 const { syncControlledPoints }: any = usePointCache();
74
77
75− const point = need.point(pointCursor);
78+ const point = Just(pointCursor)
76 const _contracts = need.contracts(contracts);
79 const _contracts = need.contracts(contracts);
77
80
78 const [numInvites, setNumInvites] = useState(
81 const [numInvites, setNumInvites] = useState(
@@ -89,7 +92,7 @@ export default function InviteCohort() {
89
92
90 useEffect(() => {
93 useEffect(() => {
91 if (invitePoints.length > 0) {
94 if (invitePoints.length > 0) {
92− getInvites(currentL2, page);
95+ getInvites(currentL2);
93 }
96 }
94 }, []); // eslint-disable-line react-hooks/exhaustive-deps
97 }, []); // eslint-disable-line react-hooks/exhaustive-deps
95
98
@@ -118,12 +121,12 @@ export default function InviteCohort() {
118 }
121 }
119
122
120 if (invitePoint) {
123 if (invitePoint) {
121− const { ticket, owner } = await wg.generateTemporaryDeterministicWallet(
124+ const { ticket, inviteWallet } = await generateInviteWallet(
122 invitePoint,
125 invitePoint,
123 _authToken
126 _authToken
124 );
127 );
125
128
126− construct(invitePoint, owner.keys.address);
129+ construct(invitePoint, inviteWallet.keys.address);
127 setL1Invite({ ticket, planet: invitePoint });
130 setL1Invite({ ticket, planet: invitePoint });
128 } else {
131 } else {
129 setError('No available planets');
132 setError('No available planets');
@@ -149,13 +152,9 @@ export default function InviteCohort() {
149 ]);
152 ]);
150
153
151 useEffect(() => {
154 useEffect(() => {
152− const storedInvites = getStoredInvites(point);
155+ const storedInvites = getStoredInvites(ls);
153
156
154− if (
155− !completed ||
156− !l1Invite ||
157− storedInvites.find(({ planet }) => planet === l1Invite.planet)
157+ if (!completed || !l1Invite || storedInvites[l1Invite.planet]) {
158− ) {
159 return;
158 return;
160 }
159 }
161
160
@@ -200,7 +199,7 @@ export default function InviteCohort() {
200 const downloadCsv = useCallback(() => {
199 const downloadCsv = useCallback(() => {
201 const generateAndDownload = async () => {
200 const generateAndDownload = async () => {
202 setDownloadingCsv(true);
201 setDownloadingCsv(true);
203− const invites = await getInvites(currentL2, page, true);
202+ const invites = await getInvites(currentL2, true);
204 if (invites) {
203 if (invites) {
205 const csv = invites.reduce(
204 const csv = invites.reduce(
206 (csvData, { ticket, planet }, ind) =>
205 (csvData, { ticket, planet }, ind) =>
@@ -229,14 +228,12 @@ export default function InviteCohort() {
229 setDownloadingCsv,
228 setDownloadingCsv,
230 setInviteGeneratingNum,
229 setInviteGeneratingNum,
231 currentL2,
230 currentL2,
232− page,
233 point,
231 point,
234 ]);
232 ]);
235
233
236 const goNextPage = useCallback(() => {
234 const goNextPage = useCallback(() => {
237 setPage(page + 1);
235 setPage(page + 1);
238− getInvites(currentL2, page + 1);
236+ }, [page]);
239− }, [currentL2, getInvites, page]);
240
237
241 const getContent = () => {
238 const getContent = () => {
242 if (hasInvites) {
239 if (hasInvites) {
@@ -252,8 +249,9 @@ export default function InviteCohort() {
252 <div className="invites">
249 <div className="invites">
253 {invitesToDisplay.map(invite => (
250 {invitesToDisplay.map(invite => (
254 <div className="invite" key={invite.planet}>
251 <div className="invite" key={invite.planet}>
252+ <div
255− <div className="invite-url">
253+ className={`invite-url ${invite.ticket ? '' : 'shortened'}`}>
256− {generateUrl(invite.ticket, invite.planet)}
254+ {generateUrlAbbreviation(invite.ticket, invite.planet)}
257 </div>
255 </div>
258 {invite.ticket ? (
256 {invite.ticket ? (
259 <CopiableWithTooltip
257 <CopiableWithTooltip
@@ -261,9 +259,11 @@ export default function InviteCohort() {
261 className="copy-invite"
259 className="copy-invite"
262 />
260 />
263 ) : (
261 ) : (
262+ <Button2
264− <div className="ticket-loader">
263+ className="secondary"
265− <LoadingSpinner foreground="black" background="white" />
264+ onClick={() => showInvite(invite.planet)}>
266− </div>
265+ Show Invite
266+ </Button2>
267 )}
267 )}
268 </div>
268 </div>
269 ))}
269 ))}
@@ -354,9 +354,8 @@ export default function InviteCohort() {
354 <Button
354 <Button
355 as={'button'}
355 as={'button'}
356 className={`generate-codes ${loading ? 'loading' : ''}`}
356 className={`generate-codes ${loading ? 'loading' : ''}`}
357− center
358− solid
359 disabled={loading}
357 disabled={loading}
358+ center
360 onClick={createInvites}>
359 onClick={createInvites}>
361 {loading
360 {loading
362 ? `Generating ${numInvites} invites...`
361 ? `Generating ${numInvites} invites...`
@@ -382,6 +381,9 @@ export default function InviteCohort() {
382 }
381 }
383
382
384 const showGenerateButton = !hasPending && !hasInvites;
383 const showGenerateButton = !hasPending && !hasInvites;
384+ const downloadingCsvText = `Generating ${
385+ invitePoints.length
386+ } codes, this could take up to ${invitePoints.length * 5} seconds...`;
385
387
386 return (
388 return (
387 <View
389 <View
@@ -397,16 +399,10 @@ export default function InviteCohort() {
397 ) : (
399 ) : (
398 <Row className="has-invites-header">
400 <Row className="has-invites-header">
399 {header}
401 {header}
400− {downloadingCsv ? (
401− <Row className="download-csv">
402− Generating {invitePoints.length} codes...
403− </Row>
404− ) : (
405− <Row className="download-csv" onClick={downloadCsv}>
402+ <Row className="download-csv" onClick={downloadCsv}>
406− <Icon icon="Download" />
403+ <Icon icon="Download" />
407− <div>CSV</div>
404+ <div>CSV</div>
408− </Row>
405+ </Row>
409− )}
410 </Row>
406 </Row>
411 )}
407 )}
412 </HeaderPane>
408 </HeaderPane>
@@ -416,10 +412,8 @@ export default function InviteCohort() {
416 </div>
412 </div>
417 {showGenerateButton && (
413 {showGenerateButton && (
418 <Button
414 <Button
419− as={'button'}
420 className="generate-button"
415 className="generate-button"
421 center
416 center
422− solid
423 onClick={() => setShowInviteForm(true)}>
417 onClick={() => setShowInviteForm(true)}>
424 Generate Codes
418 Generate Codes
425 </Button>
419 </Button>
@@ -443,6 +437,7 @@ export default function InviteCohort() {
443 Add More
437 Add More
444 </Button>
438 </Button>
445 )}
439 )}
440+ <LoadingOverlay loading={downloadingCsv} text={downloadingCsvText} />
446 </View>
441 </View>
447 );
442 );
448}
443}
src/views/Login/LoginSelector.tsx−11
@@ -59,7 +59,6 @@ export default function LoginSelector({
59 }: any = useWallet();
59 }: any = useWallet();
60
60
61 const { setPointCursor }: any = usePointCursor();
61 const { setPointCursor }: any = usePointCursor();
62− const { authenticate } = useWalletConnect();
63 const { web3 }: any = useNetwork();
62 const { web3 }: any = useNetwork();
64 const _web3 = need.web3(web3);
63 const _web3 = need.web3(web3);
65
64
@@ -68,14 +67,6 @@ export default function LoginSelector({
68 names.ACTIVATE,
67 names.ACTIVATE,
69 ]);
68 ]);
70
69
71− const connectWalletConnect = async () => {
72− resetWallet();
73− setWalletType(WALLET_TYPES.WALLET_CONNECT);
74− setPointCursor(Nothing());
75− await authenticate();
76− goHome();
77− };
78−
79 const connectMetamask = useCallback(async () => {
70 const connectMetamask = useCallback(async () => {
80 try {
71 try {
81 resetWallet();
72 resetWallet();
@@ -114,8 +105,6 @@ export default function LoginSelector({
114 const selectOption = (value: string) => () => {
105 const selectOption = (value: string) => () => {
115 if (value === NAMES.METAMASK) {
106 if (value === NAMES.METAMASK) {
116 connectMetamask();
107 connectMetamask();
117− } else if (value === NAMES.WALLET_CONNECT) {
118− connectWalletConnect();
119 } else {
108 } else {
120 setCurrentTab(value);
109 setCurrentTab(value);
121 }
110 }
src/views/Point/IssueChild.js+4−1
@@ -31,6 +31,7 @@ import BridgeForm from 'form/BridgeForm';
31import FormError from 'form/FormError';
31import 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+import useCurrentPermissions from 'lib/useCurrentPermissions';
34
35
35export function useIssueChild() {
36export function useIssueChild() {
36 const { contracts } = useNetwork();
37 const { contracts } = useNetwork();
@@ -128,11 +129,13 @@ export default function IssueChild() {
128 [construct, unconstruct]
129 [construct, unconstruct]
129 );
130 );
130
131
132+ const { isStar } = useCurrentPermissions();
133+
131 return (
134 return (
132 <View pop={pop} inset>
135 <View pop={pop} inset>
133 <Grid>
136 <Grid>
134 <Grid.Item full as={ViewHeader}>
137 <Grid.Item full as={ViewHeader}>
135− Issue Child Point
138+ Spawn {isStar ? ' Planets' : ' Stars'}
136 </Grid.Item>
139 </Grid.Item>
137
140
138 {isDefaultState && (
141 {isDefaultState && (
src/views/Point/MigrateL2.scss+15−12
@@ -147,25 +147,28 @@
147 display: flex;
147 display: flex;
148 flex-direction: row;
148 flex-direction: row;
149
149
150− .transfer, .spawn {
150+ .transfer {
151 margin: 16px 0px;
151 margin: 16px 0px;
152 cursor: pointer;
152 cursor: pointer;
153− border-radius: $medium-border-radius;
154− color: white;
155− background-color: $light-gray;
156 padding: 4px 8px;
153 padding: 4px 8px;
154+ display: flex;
155+ flex-direction: row;
156+ align-items: center;
157
157
158− &:hover {
158+ .radio {
159− opacity: 0.8;
159+ margin-right: 8px;
160− }
160+ height: 16px;
161−
161+ width: 16px;
162− &.selected {
163− background-color: black;
162+ border-color: black;
164
163
165− &:hover {
164+ &.selected {
166− opacity: 1;
165+ background-color: gray;
167 }
166 }
168 }
167 }
168+
169+ &:hover {
170+ opacity: 0.8;
171+ }
169 }
172 }
170
173
171 .transfer {
174 .transfer {
src/views/Point/MigrateL2.tsx+26−12
@@ -15,8 +15,14 @@ import * as need from 'lib/need';
15
15
16import { usePointCursor } from 'store/pointCursor';
16import { usePointCursor } from 'store/pointCursor';
17
17
18−import './MigrateL2.scss';
18+import {
19+ Box,
20+ Button,
21+ Checkbox,
22+ Icon,
23+ RadioButton,
24+ Row,
19−import { Box, Button, Checkbox, Icon, Row } from '@tlon/indigo-react';
25+} from '@tlon/indigo-react';
20import {
26import {
21 getHideMigrationMessage,
27 getHideMigrationMessage,
22 storeHideMigrationMessage,
28 storeHideMigrationMessage,
@@ -25,13 +31,13 @@ import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
25import { useNetwork } from 'store/network';
31import { useNetwork } from 'store/network';
26import { usePointCache } from 'store/pointCache';
32import { usePointCache } from 'store/pointCache';
27import useEthereumTransaction from 'lib/useEthereumTransaction';
33import useEthereumTransaction from 'lib/useEthereumTransaction';
28−import { GAS_LIMITS } from 'lib/constants';
34+import { DUMMY_L2_ADDRESS, GAS_LIMITS } from 'lib/constants';
29import Dropdown from 'components/L2/Dropdowns/Dropdown';
35import Dropdown from 'components/L2/Dropdowns/Dropdown';
30import { PointLayer } from 'lib/types/PointLayer';
36import { PointLayer } from 'lib/types/PointLayer';
31import Sigil from 'components/Sigil';
37import Sigil from 'components/Sigil';
32import { isPlanet, isStar } from 'lib/utils/point';
38import { isPlanet, isStar } from 'lib/utils/point';
33
39
34−const DUMMY_L2_ADDRESS = '0x1111111111111111111111111111111111111111';
40+import './MigrateL2.scss';
35
41
36const PointEntry = ({
42const PointEntry = ({
37 point,
43 point,
@@ -186,16 +192,24 @@ export default function MigrateL2() {
186 </Dropdown>
192 </Dropdown>
187 {star && (
193 {star && (
188 <Box className="transfer-spawn-selector">
194 <Box className="transfer-spawn-selector">
189− <Box
190− onClick={toggleTransfer}
195+ <Row onClick={toggleTransfer} className="transfer">
196+ <RadioButton
197+ color="rgba(0,0,0,0.3)"
191− className={`transfer ${transfer ? 'selected' : ''}`}>
198+ className={`radio ${transfer ? 'selected' : ''}`}
199+ selected={transfer}
200+ name="transferType"
201+ />
192 Transfer Point
202 Transfer Point
193− </Box>
203+ </Row>
194− <Box
195− onClick={toggleTransfer}
204+ <Row onClick={toggleTransfer} className="transfer">
205+ <RadioButton
206+ color="rgba(0,0,0,0.3)"
196− className={`spawn ${!transfer ? 'selected' : ''}`}>
207+ className={`radio ${!transfer ? 'selected' : ''}`}
208+ selected={!transfer}
209+ name="transferType"
210+ />
197 Set Spawn Proxy
211 Set Spawn Proxy
198− </Box>
212+ </Row>
199 </Box>
213 </Box>
200 )}
214 )}
201 {star && (
215 {star && (
src/views/Point/Residents.js+4−4
@@ -2,7 +2,6 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Grid, H4, HelpText } from 'indigo-react';
3import { Grid, H4, HelpText } from 'indigo-react';
4import { Just, Nothing } from 'folktale/maybe';
4import { Just, Nothing } from 'folktale/maybe';
5−import { azimuth } from 'azimuth-js';
6import * as ob from 'urbit-ob';
5import * as ob from 'urbit-ob';
7
6
8import View from 'components/View';
7import View from 'components/View';
@@ -21,6 +20,7 @@ import * as need from 'lib/need';
21import { usePointCursor } from 'store/pointCursor';
20import { usePointCursor } from 'store/pointCursor';
22import { usePointCache } from 'store/pointCache';
21import { usePointCache } from 'store/pointCache';
23import Footer from 'components/Footer';
22import Footer from 'components/Footer';
23+import { isGalaxy } from 'lib/utils/point';
24
24
25const NAMES = {
25const NAMES = {
26 REQUESTS: 'REQUESTS',
26 REQUESTS: 'REQUESTS',
@@ -209,10 +209,10 @@ export default function Residents() {
209 const onDecline = isRequests && _onDecline;
209 const onDecline = isRequests && _onDecline;
210
210
211 const points = isRequests ? requests : residents;
211 const points = isRequests ? requests : residents;
212− const { canSpawn, canVote } = useCurrentPermissions();
212+ const { canSpawn, canVote, isStar } = useCurrentPermissions();
213
213
214 const senateButton = (() => {
214 const senateButton = (() => {
215− if (azimuth.getPointSize(point) !== azimuth.PointSize.Galaxy) {
215+ if (!isGalaxy(point)) {
216 return null;
216 return null;
217 }
217 }
218 return (
218 return (
@@ -246,7 +246,7 @@ export default function Residents() {
246 as={ForwardButton}
246 as={ForwardButton}
247 disabled={!canSpawn}
247 disabled={!canSpawn}
248 onClick={goIssuePoint}>
248 onClick={goIssuePoint}>
249− Issue Point
249+ Spawn {isStar ? ' Planets' : ' Stars'}
250 </Grid.Item>
250 </Grid.Item>
251 <Grid.Divider />
251 <Grid.Divider />
252 {senateButton}
252 {senateButton}
src/views/UrbitID/DownloadKeys.js+42−35
@@ -1,6 +1,7 @@
1import React, { useCallback, useState } from 'react';
1import React, { useCallback, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import * as need from 'lib/need';
3import * as need from 'lib/need';
4+import { Row } from '@tlon/indigo-react';
4
5
5import { usePointCursor } from 'store/pointCursor';
6import { usePointCursor } from 'store/pointCursor';
6
7
@@ -12,8 +13,10 @@ import useKeyfileGenerator from 'lib/useKeyfileGenerator';
12
13
13import { useWallet } from 'store/wallet';
14import { useWallet } from 'store/wallet';
14import { Grid, P } from 'indigo-react';
15import { Grid, P } from 'indigo-react';
15−import ViewHeader from 'components/ViewHeader';
16import { useLocalRouter } from 'lib/LocalRouter';
16import { 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';
17
20
18export default function AdminRedownload() {
21export default function AdminRedownload() {
19 const { urbitWallet } = useWallet();
22 const { urbitWallet } = useWallet();
@@ -36,40 +39,44 @@ export default function AdminRedownload() {
36 const goBack = useCallback(() => pop(), [pop]);
39 const goBack = useCallback(() => pop(), [pop]);
37
40
38 return (
41 return (
39− <>
42+ <Window>
40− <Grid>
43+ <HeaderPane>
41− <Grid.Item full as={ViewHeader}>
44+ <Row className="header-row">
42− Download Passport
45+ <h5>Download Passport</h5>
43− </Grid.Item>
46+ </Row>
47+ </HeaderPane>
48+ <BodyPane>
49+ <Grid>
44− <Grid.Item full as={P}>
50+ <Grid.Item full as={P}>
45− After you’ve downloaded your passport, back up the ticket manually or
51+ After you’ve downloaded your passport, back up the ticket manually
46− store on a trusted device.
52+ or store on a trusted device.
47− </Grid.Item>
48− {downloaded ? (
49− <Grid.Item full as={RestartButton} solid onClick={goBack}>
50− Done
51 </Grid.Item>
53 </Grid.Item>
54+ {downloaded ? (
55+ <Grid.Item full as={RestartButton} solid onClick={goBack}>
56+ Done
57+ </Grid.Item>
52− ) : (
58+ ) : (
53− <Grid.Item
59+ <Grid.Item
54− full
60+ full
55− as={DownloadButton}
61+ as={DownloadButton}
56− solid
62+ solid
57− disabled={Nothing.hasInstance(paper)}
63+ disabled={Nothing.hasInstance(paper)}
58− onClick={doDownload}>
64+ onClick={doDownload}>
59− {paper.matchWith({
65+ {paper.matchWith({
60− Nothing: () => 'Printing and folding...',
66+ Nothing: () => 'Printing and folding...',
61− Just: _ => 'Download paper wallet',
67+ Just: _ => 'Download paper wallet',
62− })}
68+ })}
63− </Grid.Item>
69+ </Grid.Item>
64− )}
70+ )}
65− </Grid>
71+ </Grid>
66− <PaperBuilder
72+ <PaperBuilder
67− point={point}
73+ point={point}
68− wallets={[_urbitWallet]}
74+ wallets={[_urbitWallet]}
69− callback={paper => {
75+ callback={paper => {
70− setPaper(Just(paper));
76+ setPaper(Just(paper));
71− }}
77+ }}
72− />
78+ />
73− </>
79+ </BodyPane>
80+ </Window>
74 );
81 );
75}
82}
src/views/UrbitID/ResetKeys.js+15−7
@@ -11,6 +11,10 @@ import ResetConfirm from './ResetKeys/ResetConfirm';
11import ResetDownload from './ResetKeys/ResetDownload';
11import ResetDownload from './ResetKeys/ResetDownload';
12import ResetVerify from './ResetKeys/ResetVerify';
12import ResetVerify from './ResetKeys/ResetVerify';
13import ResetExecute from './ResetKeys/ResetExecute';
13import 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';
14
18
15const NAMES = {
19const NAMES = {
16 CONFIRM: 'CONFIRM',
20 CONFIRM: 'CONFIRM',
@@ -50,13 +54,17 @@ export default function ResetKeys() {
50 );
54 );
51
55
52 return (
56 return (
53− <LocalRouterProvider value={router}>
57+ <Window>
54− <Grid>
58+ <HeaderPane>
55− <Grid.Item full as={Flex} row align="center">
59+ <Row className="header-row">
56 <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
60 <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
57− </Grid.Item>
61+ </Row>
58− </Grid>
62+ </HeaderPane>
63+ <BodyPane>
64+ <LocalRouterProvider value={router}>
59− <Route newWallet={newWallet} setNewWallet={setNewWallet} />
65+ <Route newWallet={newWallet} setNewWallet={setNewWallet} />
60− </LocalRouterProvider>
66+ </LocalRouterProvider>
67+ </BodyPane>
68+ </Window>
61 );
69 );
62}
70}
src/views/UrbitID/SetProxy.js+80−71
@@ -32,6 +32,10 @@ import {
32} from 'form/validators';
32} from 'form/validators';
33import BridgeForm from 'form/BridgeForm';
33import BridgeForm from 'form/BridgeForm';
34import FormError from 'form/FormError';
34import 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';
35
39
36const getL2ProxyName = proxy =>
40const getL2ProxyName = proxy =>
37 proxy === PROXY_TYPE.MANAGEMENT
41 proxy === PROXY_TYPE.MANAGEMENT
@@ -207,83 +211,88 @@ export default function SetProxy() {
207 }, [completed, properProxyType]);
211 }, [completed, properProxyType]);
208
212
209 return (
213 return (
210− <Grid>
214+ <Window>
211− <Grid.Item full className="f5 mb2">
215+ <HeaderPane>
216+ <Row className="header-row">
212− {header}
217+ <h5>{header}</h5>
213− </Grid.Item>
218+ </Row>
214−
219+ </HeaderPane>
220+ <BodyPane>
221+ <Grid style={{ width: '100%' }}>
215− {!completed && (
222+ {!completed && (
216− <>
223+ <>
217− <Grid.Item full as={Text} className="mb4 f5 gray4">
224+ <Grid.Item full as={Text} className="mb4 f5 gray4">
218− {proxyTypeToHumanDescription(data.proxyType)}
225+ {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>
226+ </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
227+ <Grid.Item
239− flex
228+ full
240 as={Text}
229 as={Text}
241− className={cn('mono mv3 gray4', {
230+ className={cn('f6', {
242− black: !completed && isProxySet,
243− gray4: !completed && !isProxySet,
244 green3: completed,
231 green3: completed,
245 })}>
232 })}>
246− {isProxySet ? proxyAddress : 'Unset'}
233+ Current
247− </Flex.Item>
248− </Grid.Item>
234+ </Grid.Item>
235+ </>
236+ )}
249
237
250− {completed ? (
238+ <BridgeForm
251− <Grid.Item full className="mb4" />
239+ validate={validate}
252− ) : (
240+ onValues={onValues}
253− <Grid.Item
241+ initialValues={initialValues}>
254− full
242+ {({ handleSubmit, values }) => (
255− as={AddressInput}
243+ <>
256− className="mv4"
244+ <Grid.Item full as={Flex} row justify="between" align="center">
257− name="address"
245+ <Flex.Item
258− label={`New ${properProxyType} Address`}
246+ flex
259− disabled={inputsLocked || values.unset}
247+ as={Text}
260− />
248+ className={cn('mono mv3 gray4', {
249+ black: !completed && isProxySet,
250+ gray4: !completed && !isProxySet,
251+ green3: completed,
261− )}
252+ })}>
253+ {isProxySet ? proxyAddress : 'Unset'}
254+ </Flex.Item>
255+ </Grid.Item>
262
256
257+ {completed ? (
263− <Grid.Item full as={FormError} />
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+ )}
264
269
270+ <Grid.Item full as={FormError} />
271+
265− {txType === 'L2' ? (
272+ {txType === 'L2' ? (
266− <Grid.Item
273+ <Grid.Item
267− as={Button}
274+ as={Button}
268− full
275+ full
269− className="set-proxy"
276+ className="set-proxy"
270− center
277+ center
271− solid
278+ solid
272− disabled={values.unset}
279+ disabled={values.unset}
273− onClick={setProxy}>
280+ onClick={setProxy}>
274− {'Sign Transaction'}
281+ {'Sign Transaction'}
275− </Grid.Item>
282+ </Grid.Item>
276− ) : (
283+ ) : (
277− <Grid.Item
284+ <Grid.Item
278− full
285+ full
279− as={InlineEthereumTransaction}
286+ as={InlineEthereumTransaction}
280− {...bind}
287+ {...bind}
281− onReturn={() => pop()}
288+ onReturn={() => pop()}
282− />
289+ />
290+ )}
291+ </>
283 )}
292 )}
284− </>
293+ </BridgeForm>
285− )}
294+ </Grid>
286− </BridgeForm>
295+ </BodyPane>
287− </Grid>
296+ </Window>
288 );
297 );
289}
298}
src/views/UrbitID/Transfer.js+78−68
@@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Grid, Text, Button } from 'indigo-react';
3import { Grid, Text, Button } from 'indigo-react';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
5+import { Row } from '@tlon/indigo-react';
5
6
6import { useNetwork } from 'store/network';
7import { useNetwork } from 'store/network';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCursor } from 'store/pointCursor';
@@ -16,13 +17,15 @@ import useEthereumTransaction from 'lib/useEthereumTransaction';
16import { GAS_LIMITS } from 'lib/constants';
17import { GAS_LIMITS } from 'lib/constants';
17import useRoller from 'lib/useRoller';
18import useRoller from 'lib/useRoller';
18
19
19−import ViewHeader from 'components/ViewHeader';
20import NoticeBox from 'components/NoticeBox';
20import NoticeBox from 'components/NoticeBox';
21import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
21import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
22import { AddressInput } from 'form/Inputs';
22import { AddressInput } from 'form/Inputs';
23import { composeValidator, buildAddressValidator } from 'form/validators';
23import { composeValidator, buildAddressValidator } from 'form/validators';
24import BridgeForm from 'form/BridgeForm';
24import BridgeForm from 'form/BridgeForm';
25import FormError from 'form/FormError';
25import 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';
26
29
27function useTransfer() {
30function useTransfer() {
28 const { contracts } = useNetwork();
31 const { contracts } = useNetwork();
@@ -102,75 +105,82 @@ export default function AdminTransfer() {
102 );
105 );
103
106
104 return (
107 return (
108+ <Window>
109+ <HeaderPane>
110+ <Row className="header-row">
111+ <h5>Transfer Point</h5>
112+ </Row>
113+ </HeaderPane>
114+ <BodyPane>
105− <Grid gap={1}>
115+ <Grid style={{ width: '100%' }} full gap={1}>
106− <Grid.Item full as={ViewHeader}>
107− Transfer Point
108− </Grid.Item>
109−
110− <BridgeForm validate={validate} onValues={onValues}>
116+ <BridgeForm validate={validate} onValues={onValues}>
111− {({ handleSubmit, values }) => (
117+ {({ 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 && (
125 <>
118 <>
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+ <>
126− <Grid.Item full as={Text} className="wrap">
133+ <Grid.Item full as={Text} className="wrap">
127− Started transferring <span className="mono">{name}</span> to{' '}
134+ Started transferring <span className="mono">{name}</span>
135+ to{' '}
128− </Grid.Item>
136+ </Grid.Item>
129− <Grid.Item full as={Text}>
137+ <Grid.Item full as={Text}>
130− <span className="mono gray4">{values.address}</span>
138+ <span className="mono gray4">{values.address}</span>
131− </Grid.Item>
139+ </Grid.Item>
132− <Grid.Item full as={Text} className="mt6">
140+ <Grid.Item full as={Text} className="mt6">
133− Until they accept the transfer, you still own{' '}
141+ Until they accept the transfer, you still own{' '}
134− <span className="mono">{name}</span>
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'}
135− </Grid.Item>
170+ </Grid.Item>
171+ ) : (
172+ <Grid.Item
173+ full
174+ as={InlineEthereumTransaction}
175+ {...bind}
176+ onReturn={() => pop()}
177+ />
178+ )}
136 </>
179 </>
137 )}
180 )}
138−
181+ </BridgeForm>
139− {!completed && (
182+ </Grid>
140− <Grid.Item
183+ </BodyPane>
141− full
184+ </Window>
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>
175 );
185 );
176}
186}
src/views/UrbitOS/ChangeSponsor.js+35−26
@@ -23,6 +23,10 @@ import { validateMinimumPatpByteLength } from 'lib/validators';
23import { usePointCursor } from 'store/pointCursor';
23import { usePointCursor } from 'store/pointCursor';
24import useEthereumTransaction from 'lib/useEthereumTransaction';
24import useEthereumTransaction from 'lib/useEthereumTransaction';
25import { isStar } from 'lib/utils/point';
25import { 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';
26
30
27function useChangeSponsor() {
31function useChangeSponsor() {
28 const { contracts } = useNetwork();
32 const { contracts } = useNetwork();
@@ -96,32 +100,37 @@ function ChangeSponsor({ onDone }) {
96 [construct, unconstruct]
100 [construct, unconstruct]
97 );
101 );
98 return (
102 return (
99− <Grid>
103+ <Window>
100− <Grid.Item full as={ViewHeader}>
104+ <HeaderPane>
105+ <Row className="header-row">
101− Change Sponsor
106+ <h5>Change Sponsor</h5>
102− </Grid.Item>
107+ </Row>
108+ </HeaderPane>
109+ <BodyPane>
103− <BridgeForm validate={validate} onValues={onValues}>
110+ <BridgeForm validate={validate} onValues={onValues}>
104− {() => (
111+ {() => (
105− <>
112+ <Box width="100%">
106− <Grid.Item
113+ <Grid.Item
107− full
114+ full
108− as={PointInput}
115+ as={PointInput}
109− name="sponsor"
116+ name="sponsor"
110− disabled={inputsLocked}
117+ disabled={inputsLocked}
111− label="New sponsor"
118+ label="New sponsor"
119+ className="mv4"
112− />
120+ />
113− <Grid.Item full as={FormError} />
121+ <Grid.Item full as={FormError} />
114− <Grid.Item
122+ <Grid.Item
115− full
123+ full
116− as={InlineEthereumTransaction}
124+ as={InlineEthereumTransaction}
117− {...bind}
125+ {...bind}
118− onReturn={onDone}
126+ onReturn={onDone}
119− label="Request"
127+ label="Request"
120− />
128+ />
121− </>
129+ </Box>
122− )}
130+ )}
123− </BridgeForm>
131+ </BridgeForm>
124− </Grid>
132+ </BodyPane>
133+ </Window>
125 );
134 );
126}
135}
127
136
src/views/UrbitOS/Home.tsx+11−4
@@ -16,12 +16,12 @@ import { ReactComponent as KeyfileIcon } from 'assets/keyfile.svg';
16
16
17import { useLocalRouter } from 'lib/LocalRouter';
17import { useLocalRouter } from 'lib/LocalRouter';
18import { L1Point } from 'lib/types/L1Point';
18import { L1Point } from 'lib/types/L1Point';
19−import AlertBox from 'components/AlertBox';
20import useKeyfileGenerator from 'lib/useKeyfileGenerator';
19import useKeyfileGenerator from 'lib/useKeyfileGenerator';
21import { Box, Button, Row } from '@tlon/indigo-react';
20import { Box, Button, Row } from '@tlon/indigo-react';
22import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
21import CopiableWithTooltip from 'components/copiable/CopiableWithTooltip';
23
22
24import './UrbitOS.scss';
23import './UrbitOS.scss';
24+import Modal from 'components/L2/Modal';
25
25
26export default function UrbitOSHome() {
26export default function UrbitOSHome() {
27 const { pointCursor }: any = usePointCursor();
27 const { pointCursor }: any = usePointCursor();
@@ -109,14 +109,12 @@ export default function UrbitOSHome() {
109 </Button>
109 </Button>
110 {hasSetNetworkingKeys && (
110 {hasSetNetworkingKeys && (
111 <Button className="secondary" onClick={toggleShowKeys}>
111 <Button className="secondary" onClick={toggleShowKeys}>
112− {showKeys ? 'Hide' : 'View'}
112+ View
113 </Button>
113 </Button>
114 )}
114 )}
115 </Row>
115 </Row>
116 </Row>
116 </Row>
117
117
118− {showKeys && <Grid.Item full as={NetworkingKeys} point={point} />}
119−
120 {!!code && (
118 {!!code && (
121 <Row className="between-row management">
119 <Row className="between-row management">
122 <Box>
120 <Box>
@@ -129,6 +127,15 @@ export default function UrbitOSHome() {
129 </Row>
127 </Row>
130 )}
128 )}
131 </BodyPane>
129 </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>
132 </Window>
139 </Window>
133 );
140 );
134}
141}
src/views/UrbitOS/NetworkingKeys.js+19−11
@@ -23,7 +23,6 @@ import { GAS_LIMITS } from 'lib/constants';
23import { addHexPrefix } from 'lib/utils/address';
23import { addHexPrefix } from 'lib/utils/address';
24import useKeyfileGenerator from 'lib/useKeyfileGenerator';
24import useKeyfileGenerator from 'lib/useKeyfileGenerator';
25
25
26−import ViewHeader from 'components/ViewHeader';
27import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
26import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
28import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
27import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
29import NoticeBox from 'components/NoticeBox';
28import NoticeBox from 'components/NoticeBox';
@@ -39,6 +38,10 @@ import BridgeForm from 'form/BridgeForm';
39import Condition from 'form/Condition';
38import Condition from 'form/Condition';
40import FormError from 'form/FormError';
39import FormError from 'form/FormError';
41import { convertToInt } from 'lib/convertToInt';
40import { 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';
42
45
43function useSetKeys(manualNetworkSeed, setManualNetworkSeed) {
46function useSetKeys(manualNetworkSeed, setManualNetworkSeed) {
44 const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
47 const { urbitWallet, wallet, authMnemonic, authToken } = useWallet();
@@ -255,13 +258,15 @@ export default function UrbitOSNetworkingKeys({
255 'You need this keyfile to authenticate with your OS. Please Download.';
258 'You need this keyfile to authenticate with your OS. Please Download.';
256
259
257 return (
260 return (
258− <>
261+ <Window>
259− <Grid>
262+ <HeaderPane>
260− <Grid.Item full as={ViewHeader}>
263+ <Row className="header-row">
261− {viewTitle}
264+ <h5>{viewTitle}</h5>
262− </Grid.Item>
265+ </Row>
266+ </HeaderPane>
267+ <BodyPane>
263 {!completed && (
268 {!completed && (
264− <Grid.Item className="gray4 f5" full>
269+ <Grid.Item className="gray4 f5 mv2 align-self-start" full>
265 Network Keys authenticate Urbit ID with Urbit OS.
270 Network Keys authenticate Urbit ID with Urbit OS.
266 </Grid.Item>
271 </Grid.Item>
267 )}
272 )}
@@ -272,6 +277,7 @@ export default function UrbitOSNetworkingKeys({
272 className="mb3"
277 className="mb3"
273 as={DownloadKeyfileButton}
278 as={DownloadKeyfileButton}
274 solid
279 solid
280+ left
275 {...keyfileBind}
281 {...keyfileBind}
276 />
282 />
277 <Grid.Item full as={AlertBox} className="mb8">
283 <Grid.Item full as={AlertBox} className="mb8">
@@ -284,7 +290,7 @@ export default function UrbitOSNetworkingKeys({
284 onValues={onValues}
290 onValues={onValues}
285 initialValues={initialValues}>
291 initialValues={initialValues}>
286 {() => (
292 {() => (
287− <>
293+ <Box width="100%">
288 {!completed && (
294 {!completed && (
289 <>
295 <>
290 <Grid.Item
296 <Grid.Item
@@ -307,6 +313,7 @@ export default function UrbitOSNetworkingKeys({
307 name="networkSeed"
313 name="networkSeed"
308 label="Network Seed (32 bytes)"
314 label="Network Seed (32 bytes)"
309 disabled={inputsLocked}
315 disabled={inputsLocked}
316+ className="mb6"
310 />
317 />
311 </Condition>
318 </Condition>
312 <Grid.Item
319 <Grid.Item
@@ -315,6 +322,7 @@ export default function UrbitOSNetworkingKeys({
315 name="useDiscontinuity"
322 name="useDiscontinuity"
316 label="Breach Continuity"
323 label="Breach Continuity"
317 disabled={inputsLocked}
324 disabled={inputsLocked}
325+ className="mb6"
318 />
326 />
319
327
320 <Grid.Item full as={FormError} />
328 <Grid.Item full as={FormError} />
@@ -340,10 +348,10 @@ export default function UrbitOSNetworkingKeys({
340 onReturn={() => pop()}
348 onReturn={() => pop()}
341 />
349 />
342 )}
350 )}
343− </>
351+ </Box>
344 )}
352 )}
345 </BridgeForm>
353 </BridgeForm>
346− </Grid>
354+ </BodyPane>
347− </>
355+ </Window>
348 );
356 );
349}
357}
src/views/UrbitOS/UrbitOS.scss+10
@@ -1,5 +1,15 @@
1+@import 'style/shared/variables.scss';
2+
1.os-home {
3.os-home {
2 .keyfile > svg {
4 .keyfile > svg {
3 margin-right: 4px;
5 margin-right: 4px;
4 }
6 }
5}
7}
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+}