on master help sign in
cleanup and integrate with real hosting backend
8d566eaLiam Fitzgerald
package.json+1
@@ -13,6 +13,7 @@
13 "engines": {
13 "engines": {
14 "node": "11.0.0"
14 "node": "11.0.0"
15 },
15 },
16+ "proxy": "http://localhost:4000",
16 "dependencies": {
17 "dependencies": {
17 "@hot-loader/react-dom": "^16.8.6",
18 "@hot-loader/react-dom": "^16.8.6",
18 "@ledgerhq/hw-app-eth": "^5.5.0",
19 "@ledgerhq/hw-app-eth": "^5.5.0",
src/components/HostedShip.jsdeleted−153
@@ -1,153 +0,0 @@
1−import React, { useEffect, useCallback } from 'react';
2−import { Grid, ErrorText } from 'indigo-react';
3−
4−import cn from 'classnames';
5−
6−import { useHosting } from 'store/hosting';
7−
8−import BridgeForm from 'form/BridgeForm';
9−
10−import { GenerateButton, ForwardButton } from './Buttons';
11−
12−import 'style/hosted-ship.scss';
13−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
14−
15−export default function HostedShip({
16− // useHostedShip
17−
18− // additional from parent
19− className,
20−}) {
21− const ship = useHosting();
22− const { syncStatus } = ship;
23− console.log(ship);
24−
25− const { keyfile } = useKeyfileGenerator();
26−
27− const createShip = useCallback(() => ship.create(keyfile), [ship, keyfile]);
28−
29− const renderStatus = () => {
30− if (ship.missing || ship.booting) {
31− return (
32− <Grid.Item
33− full
34− as={GenerateButton}
35− solid
36− success
37− disabled={ship.booting}
38− loading={ship.booting}
39− onClick={createShip}>
40− Boot Urbit OS
41− </Grid.Item>
42− );
43− } else if (ship.running || ship.connecting) {
44− return (
45− <Grid.Item
46− full
47− as={ForwardButton}
48− solid
49− success
50− disabled={ship.connecting}
51− loading={ship.connecting}
52− onClick={ship.getEvents}>
53− Connect
54− </Grid.Item>
55− );
56− } else if (ship.connected) {
57− return (
58− <Grid.Item
59− full
60− as={GenerateButton}
61− solid
62− success
63− onClick={ship.getStatus}>
64− Disconnect
65− </Grid.Item>
66− );
67− } else if (ship.unknown || ship.querying) {
68− return (
69− <Grid.Item
70− full
71− as={GenerateButton}
72− accessory={ship.error ? '↺' : '->'}
73− solid
74− success
75− disabled={ship.querying}
76− loading={ship.querying}
77− onClick={syncStatus}>
78− Query
79− </Grid.Item>
80− );
81− }
82− };
83−
84− const renderEvents = (title, detail, events) => {
85− return (
86− <>
87− <Grid.Item
88− full
89− as={ForwardButton}
90− accessory=""
91− disabled
92− detail={detail}>
93− {title}
94− </Grid.Item>
95− <Grid.Item full>
96− <ul className="hosting-events">
97− {events.length === 0 && <li>&lt;Waiting&gt;</li>}
98− {events.map(event => (
99− <li>{event}</li>
100− ))}
101− </ul>
102− </Grid.Item>
103− </>
104− );
105− };
106−
107− return (
108− <>
109− <Grid className={cn(className, 'mt1')}>
110− <BridgeForm>
111− {() => (
112− <>
113− {ship.error && (
114− <Grid.Item full as={ErrorText} className="mv1">
115− {ship.error.message}
116− </Grid.Item>
117− )}
118− {renderStatus()}
119− {!ship.unknown && (
120− <Grid.Item
121− full
122− as={ForwardButton}
123− accessory=""
124− disabled
125− detail={ship.state}
126− detailClassName="mono">
127− Ship Status
128− </Grid.Item>
129− )}
130− </>
131− )}
132− </BridgeForm>
133−
134− {ship.connected && (
135− <>
136− <Grid.Divider />
137− {renderEvents('Run', 'Terminal events', ship.runEvents)}
138− {renderEvents(
139− 'New',
140− 'Pier creation and boot events',
141− ship.newEvents
142− )}
143− {renderEvents(
144− 'Sys',
145− 'Hosting platform events',
146− ship.sysEvents.map(e => e.object.message)
147− )}
148− </>
149− )}
150− </Grid>
151− </>
152− );
153−}
src/lib/hosting.jsnew file+24
@@ -0,0 +1,24 @@
1+export const getVaneName = code => {
2+ switch (code) {
3+ case 'a':
4+ return 'Ames';
5+ case 'b':
6+ return 'Behn';
7+ case 'c':
8+ return 'Clay';
9+ case 'd':
10+ return 'Dill';
11+ case 'e':
12+ return 'Eyre';
13+ case 'f':
14+ return 'Ford';
15+ case 'g':
16+ return 'Gall';
17+ case 'i':
18+ return 'Iris';
19+ case 'j':
20+ return 'Jael';
21+ }
22+};
23+
24+export const getVaneNumber = code => 1 + 'abcdefgij'.indexOf(code);
src/lib/routeNames.js+1
@@ -5,6 +5,7 @@ export const ROUTE_NAMES = {
5 ACTIVATE: Symbol('ACTIVATE'),
5 ACTIVATE: Symbol('ACTIVATE'),
6 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
6 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
8+ SENATE: Symbol('SENATE'),
8 //
9 //
9 LOGIN: Symbol('LOGIN'),
10 LOGIN: Symbol('LOGIN'),
10 ADMIN: Symbol('ADMIN'),
11 ADMIN: Symbol('ADMIN'),
src/lib/router.js+2
@@ -18,6 +18,8 @@ import UrbitOS from 'views/UrbitOS';
18import UrbitID from 'views/UrbitID';
18import UrbitID from 'views/UrbitID';
19import Residents from 'views/Residents';
19import Residents from 'views/Residents';
20import Adopt from 'views/Adopt';
20import Adopt from 'views/Adopt';
21+import Boot from 'views/Boot';
22+import Hosting from 'views/Hosting';
21
23
22import { ROUTE_NAMES } from './routeNames';
24import { ROUTE_NAMES } from './routeNames';
23
25
src/store/hosting.js+1−1
@@ -5,7 +5,7 @@ import useHostingStore from './lib/useHostingStore';
5export const HostingContext = createContext(null);
5export const HostingContext = createContext(null);
6
6
7export function HostingProvider({ children }) {
7export function HostingProvider({ children }) {
8− const store = useHostingStore('http://localhost:4000');
8+ const store = useHostingStore('');
9 return (
9 return (
10 <HostingContext.Provider value={store}>{children}</HostingContext.Provider>
10 <HostingContext.Provider value={store}>{children}</HostingContext.Provider>
11 );
11 );
src/store/lib/useHostingStore.js+117−86
@@ -1,9 +1,10 @@
1−import { useState, useEffect, useCallback } from 'react';
1+import { useState, useCallback, useMemo } from 'react';
2−import { Nothing } from 'folktale/maybe';
2+import { Just, Nothing } from 'folktale/maybe';
3import * as ob from 'urbit-ob';
3import * as ob from 'urbit-ob';
4
4
5+import { getVaneName, getVaneNumber } from 'lib/hosting';
5import SolarisClient from 'lib/SolarisClient';
6import SolarisClient from 'lib/SolarisClient';
6−import { usePointCursor } from 'store/pointCursor';
7+// import { usePointCursor } from 'store/pointCursor';
7import useKeyfileGenerator from 'lib/useKeyfileGenerator';
8import useKeyfileGenerator from 'lib/useKeyfileGenerator';
8const STATE = {
9const STATE = {
9 // container states
10 // container states
@@ -11,32 +12,26 @@ const STATE = {
11 MISSING: 'MISSING',
12 MISSING: 'MISSING',
12 RUNNING: 'RUNNING',
13 RUNNING: 'RUNNING',
13 PENDING: 'PENDING',
14 PENDING: 'PENDING',
14− // api states
15− QUERYING: 'QUERYING',
16− BOOTING: 'BOOTING',
17− CONNECTING: 'CONNECTING',
18− // connection states
19− CONNECTED: 'CONNECTED',
20};
15};
21
16
22−const mockFetch = status => ({
23− ok: true,
24−
25− json: () => Promise.resolve({ status }),
26−});
27−
28function useHostingStore(url) {
17function useHostingStore(url) {
29 const client = new SolarisClient(url);
18 const client = new SolarisClient(url);
30
19
31− const { pointCursor } = usePointCursor();
20+ // const { pointCursor } = usePointCursor();
21+ //
22+ const pointCursor = Just(ob.patp2dec('~wicdev-wisryt'));
23+
24+ const [bootProgress, setBootProgress] = useState(0);
25+ const [bootMessage, setBootMessage] = useState('Assembling Urbit');
26+ const [startTime] = useState(Date.now());
32
27
33− const [state, _setState] = useState(STATE.UNKNOWN);
28+ const [status, _setStatus] = useState(STATE.UNKNOWN);
34− const setState = useCallback(
29+ const setStatus = useCallback(
35 s => {
30 s => {
36 console.log(s);
31 console.log(s);
37− _setState(s);
32+ _setStatus(s);
38 },
33 },
39− [_setState]
34+ [_setStatus]
40 );
35 );
41
36
42 const [sysEvents, setSysEvents] = useState([]);
37 const [sysEvents, setSysEvents] = useState([]);
@@ -48,65 +43,25 @@ function useHostingStore(url) {
48 error => {
43 error => {
49 _setError(error);
44 _setError(error);
50 if (error) {
45 if (error) {
51− setState(STATE.UNKNOWN);
46+ setStatus(STATE.UNKNOWN);
52 console.error(error);
47 console.error(error);
53 }
48 }
54 },
49 },
55− [_setError, setState]
50+ [_setError, setStatus]
56 );
51 );
57
52
58− const unknown = state === STATE.UNKNOWN;
53+ const unknown = status === STATE.UNKNOWN;
59− const missing = state === STATE.MISSING;
54+ const missing = status === STATE.MISSING;
60− const running = state === STATE.RUNNING;
55+ const running = status === STATE.RUNNING;
61− const pending = state === STATE.PENDING;
56+ const pending = status === STATE.PENDING;
62− const querying = state === STATE.QUERYING;
57+
63− const booting = state === STATE.BOOTING;
58+ let syncStatus;
64− const connecting = state === STATE.CONNECTING;
65− const connected = state === STATE.CONNECTED;
66
59
67 const resetEvents = useCallback(() => {
60 const resetEvents = useCallback(() => {
68 setSysEvents([]);
61 setSysEvents([]);
69 setNewEvents([]);
62 setNewEvents([]);
70 setRunEvents([]);
63 setRunEvents([]);
71− }, []);
64+ }, [setSysEvents, setNewEvents, setRunEvents]);
72−
73− const syncStatus = useCallback(async () => {
74− try {
75− if (Nothing.hasInstance(pointCursor)) {
76− return;
77− }
78− const patp = ob.patp(pointCursor.value);
79− setError(undefined);
80−
81− resetEvents();
82−
83− setState(STATE.QUERYING);
84−
85− const response = await client.getShipsByPatp(patp);
86−
87− if (response.status === 404) {
88− setState(STATE.MISSING);
89− } else if (!response.ok) {
90− setError(response.statusText);
91− } else {
92− const ship = await response.json();
93−
94− if (ship.status === 'Pending') {
95− setState(STATE.PENDING);
96− } else if (ship.status === 'Running') {
97− setState(STATE.RUNNING);
98− } else {
99− setError(ship);
100− }
101− }
102− } catch (error) {
103− setError(error);
104− }
105− }, [client, pointCursor, resetEvents, setError, setState]);
106−
107− // useEffect(() => {
108− // syncStatus();
109− // }, [pointCursor, syncStatus]);
110
65
111 const getEvents = useCallback(() => {
66 const getEvents = useCallback(() => {
112 try {
67 try {
@@ -118,29 +73,53 @@ function useHostingStore(url) {
118
73
119 resetEvents();
74 resetEvents();
120
75
121− setState(STATE.CONNECTING);
122−
123 const url = client.getShipsByPatpUrl(patp) + '/events';
76 const url = client.getShipsByPatpUrl(patp) + '/events';
124 const source = new EventSource(url);
77 const source = new EventSource(url);
125
78
126− source.onopen = () => setState(STATE.CONNECTED);
127− // source.onerror = () => setState(STATE.UNKNOWN);
79+ // source.onerror = () => setStatus(STATE.UNKNOWN);
128 // source.onmessage = console.log;
80 // source.onmessage = console.log;
129
81
130 source.addEventListener('sys', event => {
82 source.addEventListener('sys', event => {
131− console.log(event);
83+ // console.log(event);
84+ const sysEvent = JSON.parse(event.data);
85+ const time = new Date(sysEvent.object.firstTimestamp).getTime();
86+ if (time > startTime) {
87+ console.log(sysEvent);
132− setSysEvents(previous => [...previous, JSON.parse(event.data)]);
88+ setSysEvents(previous => [...previous, sysEvent]);
89+ }
133 });
90 });
134
91
135 source.addEventListener('new', event => {
92 source.addEventListener('new', event => {
136− console.log(event);
93+ // console.log(event);
94+ const elapsed = (Date.now() - startTime) / 1000;
137 if (event.data !== '') {
95 if (event.data !== '') {
138 setNewEvents(previous => [...previous, event.data]);
96 setNewEvents(previous => [...previous, event.data]);
139 }
97 }
98+ if (event.data === '1-b') {
99+ setBootMessage('Compiling Hoon');
100+ setBootProgress(0.2);
101+ console.log(`${elapsed}: compiling Hoon`);
102+ } else if (event.data === '1-c') {
103+ setBootProgress(0.25);
104+ } else if (event.data === '%arvo-assembly') {
105+ setBootMessage('Compiling Urbit OS');
106+ setBootProgress(0.3);
107+ } else if (event.data.startsWith('[%vane %')) {
108+ const vane = getVaneName(event.data.slice(8, 9));
109+ setBootMessage(`Compiling vane: ${vane}`);
110+ setBootProgress(0.3 + 0.05 * getVaneNumber(event.data.slice(8, 9)));
111+ } else if (event.data === 'pier: boot complete') {
112+ setBootMessage('Ship starting');
113+ setBootProgress(0.85);
114+ }
140 });
115 });
141
116
142 source.addEventListener('run', event => {
117 source.addEventListener('run', event => {
143− console.log(event);
118+ if (event.data === 'goad: recompiling all apps') {
119+ setBootProgress(1.0);
120+ setBootMessage('Ship Launched');
121+ syncStatus();
122+ }
144 if (event.data !== '') {
123 if (event.data !== '') {
145 setRunEvents(previous => [...previous, event.data]);
124 setRunEvents(previous => [...previous, event.data]);
146 }
125 }
@@ -148,7 +127,54 @@ function useHostingStore(url) {
148 } catch (error) {
127 } catch (error) {
149 setError(error);
128 setError(error);
150 }
129 }
130+ }, [
131+ setError,
132+ resetEvents,
133+ client,
134+ pointCursor,
135+ setBootMessage,
136+ startTime,
137+ syncStatus,
138+ ]);
139+
140+ syncStatus = useCallback(async () => {
141+ try {
142+ if (Nothing.hasInstance(pointCursor)) {
143+ return;
144+ }
145+ const patp = ob.patp(pointCursor.value);
146+ setError(undefined);
147+
151− }, [setError, resetEvents, client, pointCursor, setState]);
148+ resetEvents();
149+
150+ const response = await client.getShipsByPatp(patp);
151+
152+ if (response.status === 404) {
153+ setStatus(STATE.MISSING);
154+ } else if (!response.ok) {
155+ setError(response.statusText);
156+ } else {
157+ const ship = await response.json();
158+
159+ if (ship.status === 'Pending') {
160+ setStatus(STATE.PENDING);
161+ getEvents();
162+ } else if (ship.status === 'Running') {
163+ setStatus(STATE.RUNNING);
164+ } else {
165+ setError(ship);
166+ }
167+ }
168+ } catch (error) {
169+ setError(error);
170+ }
171+ }, [client, getEvents, pointCursor, resetEvents, setError, setStatus]);
172+
173+ const hostedShipUrl = useMemo(
174+ () =>
175+ `http://${ob.patp(pointCursor.getOrElse('')).slice(1)}.liam.tlon.network`,
176+ [pointCursor]
177+ );
152
178
153 const create = useCallback(
179 const create = useCallback(
154 async keyfile => {
180 async keyfile => {
@@ -156,10 +182,15 @@ function useHostingStore(url) {
156 if (Nothing.hasInstance(pointCursor)) {
182 if (Nothing.hasInstance(pointCursor)) {
157 return;
183 return;
158 }
184 }
185+ keyfile =
186+ '0wnE.Z56pm.i2HLZ.6Pf1R.tAVZH.65R~C.Zgy6y.caI5Z.bZ2vV.09M5A.QP5-p.29UfR.35h2U.2k8-D.QP~~p.6bL62.~x3Em.t9h0x.0y01N.w80c1';
159− const patp = ob.patp(pointCursor.value);
187+ const patp = ob.patp(pointCursor.value).slice(1);
160 setError(undefined);
188 setError(undefined);
161
189
162− setState(STATE.BOOTING);
190+ const start = Date.now();
191+ console.log(start);
192+ setStatus(STATE.PENDING);
193+ setBootProgress(0.1);
163
194
164 const response = await client.postShips({
195 const response = await client.postShips({
165 patp,
196 patp,
@@ -177,7 +208,7 @@ function useHostingStore(url) {
177 setError(error);
208 setError(error);
178 }
209 }
179 },
210 },
180− [pointCursor, setError, setState, client, getEvents]
211+ [pointCursor, setError, setStatus, client, getEvents]
181 );
212 );
182
213
183 return {
214 return {
@@ -186,10 +217,7 @@ function useHostingStore(url) {
186 missing,
217 missing,
187 running,
218 running,
188 pending,
219 pending,
189− querying,
220+ url: hostedShipUrl,
190− booting,
191− connecting,
192− connected,
193
221
194 sysEvents,
222 sysEvents,
195 newEvents,
223 newEvents,
@@ -199,8 +227,11 @@ function useHostingStore(url) {
199 getEvents,
227 getEvents,
200 create,
228 create,
201
229
230+ bootProgress,
231+ bootMessage,
232+
202 // debugging
233 // debugging
203− state,
234+ status,
204 };
235 };
205}
236}
206
237
src/views/Boot.jsdeleted−7
@@ -1,7 +0,0 @@
1−import React from 'react';
2−
3−import View from 'components/View';
4−
5−export default function Boot() {
6− return <View />;
7−}
src/views/Hosting.jsdeleted−63
@@ -1,63 +0,0 @@
1−import React from 'react';
2−import { Grid } from 'indigo-react';
3−import { Just } from 'folktale/maybe';
4−import ob from 'urbit-ob';
5−
6−import { usePointCursor } from 'store/pointCursor';
7−import { useWallet } from 'store/wallet';
8−
9−import * as need from 'lib/need';
10−import { useLocalRouter } from 'lib/LocalRouter';
11−import { useHostedShip } from 'lib/useHostedShip';
12−import SolarisClient from 'lib/SolarisClient';
13−
14−import View from 'components/View';
15−import Passport from 'components/Passport';
16−import { OutButton, ForwardButton } from 'components/Buttons';
17−import HostedShip from 'components/HostedShip';
18−import Footer from 'components/Footer';
19−
20−export default function Hosting() {
21− const { pop } = useLocalRouter();
22−
23− const { pointCursor } = usePointCursor();
24− const point = need.point(pointCursor);
25− const patp = ob.patp(point);
26−
27− const { wallet } = useWallet();
28− const address = need.addressFromWallet(wallet);
29−
30− const client = new SolarisClient('https://localhost:3030');
31− const ship = useHostedShip(client, patp);
32−
33− return (
34− <View pop={pop} inset>
35− <Grid>
36− <Passport
37− point={Just(point)}
38− address={Just(address)}
39− animationMode={'slide'}
40− />
41− <Grid.Item full as={HostedShip} ship={ship} onReturn={() => pop()} />
42− <Grid.Divider />
43− </Grid>
44−
45− <Footer>
46− <Grid>
47− <Grid.Divider />
48− <Grid.Item full as={ForwardButton} disabled={!ship.running}>
49− Redirect to Urbit OS Dojo
50− </Grid.Item>
51− <Grid.Divider />
52− <Grid.Item
53− full
54− as={OutButton}
55− href="https://urbit.org/docs/getting-started"
56− detail="Run your own Urbit OS">
57− Hosting Instructions
58− </Grid.Item>
59− </Grid>
60− </Footer>
61− </View>
62− );
63−}
src/views/Invite/Email.js+1−1
@@ -103,7 +103,7 @@ const InviteMail = ({ setSubmitting }) => {
103 form.reset();
103 form.reset();
104 }, 1500);
104 }, 1500);
105 },
105 },
106− [generateInvites, setCount, setStatus, sendInvites, setSubmitting]
106+ [setSubmitting, generateInvites, sendInvites, resetInvites]
107 );
107 );
108
108
109 const validateHasReceived = useCallback(
109 const validateHasReceived = useCallback(
src/views/Point.js−27
@@ -10,14 +10,12 @@ import { useWallet } from 'store/wallet';
10import View from 'components/View';
10import View from 'components/View';
11import Greeting from 'components/Greeting';
11import Greeting from 'components/Greeting';
12import Passport from 'components/Passport';
12import Passport from 'components/Passport';
13−import { ForwardButton } from 'components/Buttons';
14import Blinky, { matchBlinky } from 'components/Blinky';
13import Blinky, { matchBlinky } from 'components/Blinky';
15import BarGraph from 'components/BarGraph';
14import BarGraph from 'components/BarGraph';
16import Chip from 'components/Chip';
15import Chip from 'components/Chip';
17import InviteSigilList from 'components/InviteSigilList';
16import InviteSigilList from 'components/InviteSigilList';
18import { ForwardButton, HostingButton } from 'components/Buttons';
17import { ForwardButton, HostingButton } from 'components/Buttons';
19import CopyButton from 'components/CopyButton';
18import CopyButton from 'components/CopyButton';
20−import { matchBlinky } from 'components/Blinky';
21import DownloadSigilButton from 'components/DownloadSigilButton';
19import DownloadSigilButton from 'components/DownloadSigilButton';
22
20
23import * as need from 'lib/need';
21import * as need from 'lib/need';
@@ -72,8 +70,6 @@ export default function Point() {
72 //
70 //
73 // availableInvites.getOrElse(0) === 0
71 // availableInvites.getOrElse(0) === 0
74
72
75− const goAdmin = useCallback(() => push(names.ADMIN), [push, names]);
76−
77 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
73 const goSenate = useCallback(() => push(names.SENATE), [push, names]);
78
74
79 const goSigil = useCallback(() => push(names.SIGIL_GENERATOR), [push, names]);
75 const goSigil = useCallback(() => push(names.SIGIL_GENERATOR), [push, names]);
@@ -288,29 +284,6 @@ export default function Point() {
288 </>
284 </>
289 )}
285 )}
290 {senateButton}
286 {senateButton}
291− <Grid.Item
292− full
293− as={ForwardButton}
294− detail="Boot your computer"
295− onClick={goBoot}>
296− Boot Urbit OS
297− </Grid.Item>
298−
299− <Grid.Item full as={HostingButton} onClick={goHosting} />
300− <Grid.Divider />
301−
302− <Grid.Item
303− full
304− as={ForwardButton}
305− accessory={code && <CopyButton text={code} />}
306− detail={code || notice}
307− disabled={!code}
308− detailClassName="mono">
309− Login Code
310− </Grid.Item>
311− <Grid.Divider />
312− <Grid.Item full as={DownloadSigilButton} point={point} />
313− <Grid.Divider />
314 </Grid>
287 </Grid>
315 </View>
288 </View>
316 );
289 );
src/views/Residents.js+34−44
@@ -1,6 +1,6 @@
1−import React, { useState, useEffect, useCallback } from 'react';
1+import React, { useState, useEffect, useCallback, useMemo } from 'react';
2import { Grid, H4, HelpText } from 'indigo-react';
2import { Grid, H4, HelpText } from 'indigo-react';
3−import { Nothing } from 'folktale/maybe';
3+import { Just, Nothing } from 'folktale/maybe';
4import * as ob from 'urbit-ob';
4import * as ob from 'urbit-ob';
5
5
6import View from 'components/View';
6import View from 'components/View';
@@ -8,6 +8,7 @@ import Tabs from 'components/Tabs';
8import Crumbs from 'components/Crumbs';
8import Crumbs from 'components/Crumbs';
9import Sigil from 'components/Sigil';
9import Sigil from 'components/Sigil';
10import Blinky from 'components/Blinky';
10import Blinky from 'components/Blinky';
11+import NavHeader from 'components/NavHeader';
11
12
12import { useLocalRouter } from 'lib/LocalRouter';
13import { useLocalRouter } from 'lib/LocalRouter';
13import useCurrentPointName from 'lib/useCurrentPointName';
14import useCurrentPointName from 'lib/useCurrentPointName';
@@ -27,26 +28,32 @@ const OPTIONS = [
27];
28];
28
29
29const VIEWS = {
30const VIEWS = {
30− [NAMES.REQUESTS]: Requests,
31+ [NAMES.REQUESTS]: Tab,
31− [NAMES.ALL]: AllResidents,
32+ [NAMES.ALL]: Tab,
32};
33};
33
34
34−function Requests({ className, requests, onAccept, onDecline }) {
35+function Tab({ className, points, onAccept, onDecline }) {
35− if (Nothing.hasInstance(requests)) {
36+ if (Nothing.hasInstance(points)) {
36− return (
37− <Grid className={className}>
37+ return <Grid className={className}></Grid>;
38− <Grid.Item full as={HelpText} className="mt8 t-center">
39− <Blinky /> Loading...
40− </Grid.Item>
41− </Grid>
42− );
43 }
38 }
44
39
45− const _requests = requests.getOrElse([]);
40+ const _points = points.getOrElse([]);
46
41
47 return (
42 return (
48 <Grid className={className}>
43 <Grid className={className}>
44+ {_points.length === 0 && (
45+ <Grid.Item full as={HelpText} className="mt8 t-center">
46+ {Just.hasInstance(points) ? (
47+ 'No points to display'
48+ ) : (
49+ <>
50+ <Blinky /> Loading...
51+ </>
52+ )}
53+ </Grid.Item>
54+ )}
55+
49− {_requests.map(point => (
56+ {_points.map(point => (
50 <Resident
57 <Resident
51 key={point}
58 key={point}
52 point={point}
59 point={point}
@@ -100,26 +107,6 @@ function Resident({ point, onAccept, onDecline }) {
100 );
107 );
101}
108}
102
109
103−function AllResidents({ className, residents }) {
104− if (Nothing.hasInstance(residents)) {
105− return (
106− <Grid className={className}>
107− <Grid.Item full>Loading...</Grid.Item>
108− </Grid>
109− );
110− }
111−
112− const _residents = residents.getOrElse([]);
113−
114− return (
115− <Grid className={className}>
116− {_residents.map(point => (
117− <Resident key={point} point={point} />
118− ))}
119− </Grid>
120− );
121−}
122−
123export default function Residents() {
110export default function Residents() {
124 const { pop, push, names } = useLocalRouter();
111 const { pop, push, names } = useLocalRouter();
125 const name = useCurrentPointName();
112 const name = useCurrentPointName();
@@ -136,28 +123,32 @@ export default function Residents() {
136
123
137 const [currentTab, setCurrentTab] = useState(NAMES.ALL);
124 const [currentTab, setCurrentTab] = useState(NAMES.ALL);
138
125
126+ const isRequests = useMemo(() => currentTab === NAMES.REQUESTS, [currentTab]);
127+
139− const onAccept = useCallback(adoptee =>
128+ const _onAccept = useCallback(adoptee =>
140 push(names.ADOPT, {
129 push(names.ADOPT, {
141 adoptee,
130 adoptee,
142 denied: false,
131 denied: false,
143 })
132 })
144 );
133 );
134+ const onAccept = isRequests && _onAccept;
145
135
146− const onDecline = useCallback(adoptee =>
136+ const _onDecline = useCallback(adoptee =>
147 push(names.ADOPT, {
137 push(names.ADOPT, {
148 adoptee,
138 adoptee,
149 denied: true,
139 denied: true,
150 })
140 })
151 );
141 );
142+ const onDecline = isRequests && _onDecline;
143+
144+ const points = isRequests ? requests : residents;
152
145
153 return (
146 return (
154 <View pop={pop} inset>
147 <View pop={pop} inset>
148+ <NavHeader>
149+ <Crumbs routes={[{ text: name, action: pop }, { text: 'Residents' }]} />
150+ </NavHeader>
155 <Grid>
151 <Grid>
156− <Grid.Item
157− full
158− as={Crumbs}
159− routes={[{ text: name, action: pop }, { text: 'Residents' }]}
160− />
161 <Grid.Item full as={H4} className="mt4">
152 <Grid.Item full as={H4} className="mt4">
162 Residents
153 Residents
163 </Grid.Item>
154 </Grid.Item>
@@ -171,8 +162,7 @@ export default function Residents() {
171 currentTab={currentTab}
162 currentTab={currentTab}
172 onTabChange={setCurrentTab}
163 onTabChange={setCurrentTab}
173 // Props for tab
164 // Props for tab
174− residents={residents}
165+ points={points}
175− requests={requests}
176 onAccept={onAccept}
166 onAccept={onAccept}
177 onDecline={onDecline}
167 onDecline={onDecline}
178 />
168 />
src/views/UrbitID.js+17−11
@@ -9,23 +9,27 @@ import { LocalRouterProvider } from 'lib/LocalRouter';
9
9
10import View from 'components/View';
10import View from 'components/View';
11import Crumbs from 'components/Crumbs';
11import Crumbs from 'components/Crumbs';
12+import NavHeader from 'components/NavHeader';
12
13
13import UrbitIDHome from './UrbitID/Home';
14import UrbitIDHome from './UrbitID/Home';
14import SigilGenerator from './UrbitID/SigilGenerator';
15import SigilGenerator from './UrbitID/SigilGenerator';
15import DownloadKeys from './UrbitID/DownloadKeys';
16import DownloadKeys from './UrbitID/DownloadKeys';
16import SetProxy from './UrbitID/SetProxy';
17import SetProxy from './UrbitID/SetProxy';
18+import ResetKeys from './UrbitID/ResetKeys';
17
19
18const NAMES = {
20const NAMES = {
19 HOME: 'HOME',
21 HOME: 'HOME',
20 SIGIL_GENERATOR: 'SIGIL_GENERATOR',
22 SIGIL_GENERATOR: 'SIGIL_GENERATOR',
21 DOWNLOAD_KEYS: 'DOWNLOAD_KEYS',
23 DOWNLOAD_KEYS: 'DOWNLOAD_KEYS',
22 SET_PROXY: 'SET_PROXY',
24 SET_PROXY: 'SET_PROXY',
25+ RESET_KEYS: 'RESET_KEYS',
23};
26};
24
27
25const VIEWS = {
28const VIEWS = {
26 [NAMES.HOME]: UrbitIDHome,
29 [NAMES.HOME]: UrbitIDHome,
27 [NAMES.SIGIL_GENERATOR]: SigilGenerator,
30 [NAMES.SIGIL_GENERATOR]: SigilGenerator,
28 [NAMES.DOWNLOAD_KEYS]: DownloadKeys,
31 [NAMES.DOWNLOAD_KEYS]: DownloadKeys,
32+ [NAMES.RESET_KEYS]: ResetKeys,
29 [NAMES.SET_PROXY]: SetProxy,
33 [NAMES.SET_PROXY]: SetProxy,
30};
34};
31
35
@@ -37,6 +41,8 @@ const humanizeName = name => {
37 return 'Download Keys';
41 return 'Download Keys';
38 case NAMES.SET_PROXY:
42 case NAMES.SET_PROXY:
39 return 'Set Key';
43 return 'Set Key';
44+ case NAMES.RESET_KEYS:
45+ return 'Reset Keys';
40 default:
46 default:
41 return undefined;
47 return undefined;
42 }
48 }
@@ -58,18 +64,18 @@ export default function UrbitOS() {
58
64
59 return (
65 return (
60 <View pop={router.pop} inset>
66 <View pop={router.pop} inset>
67+ <NavHeader>
68+ <Crumbs
69+ routes={[
70+ { text: name, action: () => history.pop() },
71+ { text: 'Urbit ID', action: homeAction },
72+ ...lastCrumb,
73+ ]}
74+ />
75+ </NavHeader>
76+
61 <LocalRouterProvider value={router}>
77 <LocalRouterProvider value={router}>
62− <Grid className="mb4">
78+ <Grid className="mb4"></Grid>
63− <Grid.Item
64− full
65− as={Crumbs}
66− routes={[
67− { text: name, action: () => history.pop() },
68− { text: 'Urbit ID', action: homeAction },
69− ...lastCrumb,
70− ]}
71− />
72− </Grid>
73 <Route />
79 <Route />
74 </LocalRouterProvider>
80 </LocalRouterProvider>
75 </View>
81 </View>
src/views/UrbitID/Home.js+5−1
@@ -35,6 +35,8 @@ export default function UrbitIDHome() {
35 names,
35 names,
36 ]);
36 ]);
37
37
38+ const goResetKeys = useCallback(() => push(names.RESET_KEYS), [push, names]);
39+
38 const isMasterTicket = Just.hasInstance(urbitWallet);
40 const isMasterTicket = Just.hasInstance(urbitWallet);
39 const _contracts = need.contracts(contracts);
41 const _contracts = need.contracts(contracts);
40 const point = need.point(pointCursor);
42 const point = need.point(pointCursor);
@@ -96,7 +98,7 @@ export default function UrbitIDHome() {
96 <Grid.Item
98 <Grid.Item
97 full
99 full
98 as={ForwardButton}
100 as={ForwardButton}
99− onClick={goDownloadKeys}
101+ onClick={goResetKeys}
100 detail="Reset Master Key and all other keys">
102 detail="Reset Master Key and all other keys">
101 Reset Keys
103 Reset Keys
102 </Grid.Item>
104 </Grid.Item>
@@ -122,6 +124,8 @@ export default function UrbitIDHome() {
122
124
123 {isParent && renderProxyAction(PROXY_TYPE.SPAWN, details.spawnProxy)}
125 {isParent && renderProxyAction(PROXY_TYPE.SPAWN, details.spawnProxy)}
124
126
127+ {isParent && networkRevision !== 0 && <Grid.Divider />}
128+
125 {isParent && networkRevision === 0 && (
129 {isParent && networkRevision === 0 && (
126 <>
130 <>
127 <Grid.Item
131 <Grid.Item
src/views/UrbitID/ResetKeys.jsnew file+64
@@ -0,0 +1,64 @@
1+import React, { useCallback, useState } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
3+import { Grid, Flex } from 'indigo-react';
4+
5+import { LocalRouterProvider } from 'lib/LocalRouter';
6+import useRouter from 'lib/useRouter';
7+
8+import Steps from 'components/Steps';
9+
10+import { usePointCursor } from 'store/pointCursor';
11+
12+import ResetConfirm from './ResetKeys/ResetConfirm';
13+import ResetDownload from './ResetKeys/ResetDownload';
14+import ResetVerify from './ResetKeys/ResetVerify';
15+import ResetExecute from './ResetKeys/ResetExecute';
16+
17+const NAMES = {
18+ CONFIRM: 'CONFIRM',
19+ DOWNLOAD: 'DOWNLOAD',
20+ VERIFY: 'VERIFY',
21+ EXECUTE: 'EXECUTE',
22+};
23+
24+const VIEWS = {
25+ CONFIRM: ResetConfirm,
26+ DOWNLOAD: ResetDownload,
27+ VERIFY: ResetVerify,
28+ EXECUTE: ResetExecute,
29+};
30+
31+const STEPS = [NAMES.CONFIRM, NAMES.DOWNLOAD, NAMES.VERIFY, NAMES.EXECUTE];
32+
33+export default function ResetKeys() {
34+ const { Route, ...router } = useRouter({
35+ names: NAMES,
36+ views: VIEWS,
37+ initialRoutes: [{ key: NAMES.CONFIRM }],
38+ });
39+
40+ const [newWallet, _setNewWallet] = useState(Nothing());
41+
42+ const setNewWallet = useCallback(
43+ (wallet, paper) => {
44+ _setNewWallet(
45+ Just({
46+ wallet,
47+ paper,
48+ })
49+ );
50+ },
51+ [_setNewWallet]
52+ );
53+
54+ return (
55+ <LocalRouterProvider value={router}>
56+ <Grid>
57+ <Grid.Item full as={Flex} row align="center">
58+ <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
59+ </Grid.Item>
60+ </Grid>
61+ <Route newWallet={newWallet} setNewWallet={setNewWallet} />
62+ </LocalRouterProvider>
63+ );
64+}
src/views/UrbitID/ResetKeys/ResetConfirm.jsnew file+91
@@ -0,0 +1,91 @@
1+import React, { useState, useCallback } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
3+import { Grid, Text } from 'indigo-react';
4+import * as need from 'lib/need';
5+
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import useLifecycle from 'lib/useLifecycle';
9+import { generateWallet } from 'lib/invite';
10+import { useLocalRouter } from 'lib/LocalRouter';
11+import { isDevelopment } from 'lib/flags';
12+
13+import { ForwardButton } from 'components/Buttons';
14+import PaperBuilder from 'components/PaperBuilder';
15+import WarningBox from 'components/WarningBox';
16+import { blinkIf } from 'components/Blinky';
17+import useCurrentPointName from 'lib/useCurrentPointName';
18+
19+export default function ResetConfirm({ newWallet, setNewWallet }) {
20+ const { push, names } = useLocalRouter();
21+ const { pointCursor } = usePointCursor();
22+ const point = need.point(pointCursor);
23+ const name = useCurrentPointName();
24+
25+ const [generatedWallet, setGeneratedWallet] = useState(Nothing());
26+
27+ // generating new wallet on mount, conveniently using it to 'force' users
28+ // to read the disclaimer text
29+ //TODO also check we can pay for the transactions?
30+ useLifecycle(() => {
31+ let mounted = true;
32+ (async () => {
33+ if (Just.hasInstance(newWallet)) {
34+ // skip if already computed (hitting the back button for example)
35+ return;
36+ }
37+
38+ const wal = await generateWallet(point);
39+ if (isDevelopment) {
40+ console.log(`The new ticket for ${name} is ${wal.ticket}`);
41+ }
42+ if (!mounted) {
43+ return;
44+ }
45+
46+ setGeneratedWallet(Just(wal));
47+ })();
48+
49+ return () => (mounted = false);
50+ });
51+
52+ const goDownload = useCallback(() => push(names.DOWNLOAD), [push, names]);
53+
54+ const paperRenderer = generatedWallet.matchWith({
55+ Nothing: () => null,
56+ Just: ({ value: wallet }) => (
57+ <PaperBuilder
58+ point={point}
59+ wallets={[wallet]}
60+ callback={paper => {
61+ setNewWallet(wallet, paper);
62+ }}
63+ />
64+ ),
65+ });
66+
67+ return (
68+ <Grid className="mt4">
69+ <Grid.Item full as={Text}>
70+ Reticketing is the process of generating a completely fresh wallet and
71+ transferring ownership of your point to that wallet.
72+ </Grid.Item>
73+ <Grid.Item full as={WarningBox} className="mt4">
74+ Beware, this resets your proxy addresses; if you're using smart
75+ contracts, this might break them! It will also change your networking
76+ keys!
77+ </Grid.Item>
78+ <Grid.Item
79+ full
80+ as={ForwardButton}
81+ solid
82+ className="mt4"
83+ accessory={blinkIf(Nothing.hasInstance(newWallet))}
84+ disabled={Nothing.hasInstance(newWallet)}
85+ onClick={goDownload}>
86+ I understand, continue
87+ </Grid.Item>
88+ {paperRenderer}
89+ </Grid>
90+ );
91+}
src/views/UrbitID/ResetKeys/ResetDownload.jsnew file+39
@@ -0,0 +1,39 @@
1+import React, { useState, useCallback } from 'react';
2+import { Nothing } from 'folktale/maybe';
3+import { Grid, Text } from 'indigo-react';
4+
5+import { useLocalRouter } from 'lib/LocalRouter';
6+import { downloadWallet } from 'lib/invite';
7+
8+import { ForwardButton, DownloadButton } from 'components/Buttons';
9+
10+export default function ReticketDownload({ newWallet }) {
11+ const { push, names } = useLocalRouter();
12+
13+ const [downloaded, setDownloaded] = useState(false);
14+
15+ const download = () => {
16+ downloadWallet(newWallet.value.paper);
17+ setDownloaded(true);
18+ };
19+
20+ const goVerify = useCallback(() => push(names.VERIFY), [push, names]);
21+
22+ return (
23+ <Grid className="mt4">
24+ <Grid.Item full as={Text}>
25+ Download the new passport, and keep it somewhere safe!
26+ </Grid.Item>
27+ <Grid.Item
28+ full
29+ as={!downloaded ? DownloadButton : ForwardButton}
30+ className="mt4"
31+ disabled={Nothing.hasInstance(newWallet)}
32+ onClick={!downloaded ? download : goVerify}
33+ success={downloaded}
34+ solid>
35+ {!downloaded ? 'Download Passport' : 'Continue'}
36+ </Grid.Item>
37+ </Grid>
38+ );
39+}
src/views/UrbitID/ResetKeys/ResetExecute.jsnew file+193
@@ -0,0 +1,193 @@
1+import React, { useState, useCallback } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
3+import { Grid, H4, Text, ErrorText } from 'indigo-react';
4+
5+import {
6+ reticketPointBetweenWallets,
7+ TRANSACTION_PROGRESS,
8+} from 'lib/reticket';
9+import * as need from 'lib/need';
10+import useLifecycle from 'lib/useLifecycle';
11+import { WALLET_TYPES } from 'lib/wallet';
12+import convertToInt from 'lib/convertToInt';
13+import useBlockWindowClose from 'lib/useBlockWindowClose';
14+
15+import { useNetwork } from 'store/network';
16+import { useWallet } from 'store/wallet';
17+import { usePointCursor } from 'store/pointCursor';
18+import { usePointCache } from 'store/pointCache';
19+import { useHistory } from 'store/history';
20+
21+import { RestartButton, ForwardButton } from 'components/Buttons';
22+import WarningBox from 'components/WarningBox';
23+import LoadingBar from 'components/LoadingBar';
24+import NeedFundsNotice from 'components/NeedFundsNotice';
25+
26+const labelForProgress = progress => {
27+ if (progress <= 0) {
28+ return 'Starting...';
29+ } else if (progress <= TRANSACTION_PROGRESS.GENERATING) {
30+ return 'Generating Ethereum Transactions...';
31+ } else if (progress <= TRANSACTION_PROGRESS.SIGNING) {
32+ return 'Signing Ethereum Transactions...';
33+ } else if (progress <= TRANSACTION_PROGRESS.FUNDING) {
34+ return 'Funding Ethereum Transactions...';
35+ } else if (progress <= TRANSACTION_PROGRESS.TRANSFERRING) {
36+ return 'Transferring ID...';
37+ } else if (progress <= TRANSACTION_PROGRESS.CLEANING) {
38+ return 'Cleaning Up...';
39+ } else if (progress <= TRANSACTION_PROGRESS.DONE) {
40+ return 'Done';
41+ }
42+};
43+
44+export default function ResetExecute({ newWallet, setNewWallet }) {
45+ const { popTo, names, reset } = useHistory();
46+ const { web3, contracts, networkType } = useNetwork();
47+ const {
48+ wallet,
49+ setWalletType,
50+ resetWallet,
51+ setUrbitWallet,
52+ walletType,
53+ walletHdPath,
54+ } = useWallet();
55+ const { pointCursor } = usePointCursor();
56+ const { getDetails } = usePointCache();
57+
58+ const [generalError, setGeneralError] = useState();
59+ const [progress, setProgress] = useState(0);
60+ const [needFunds, setNeedFunds] = useState();
61+ const isDone = progress >= 1.0;
62+
63+ useBlockWindowClose();
64+
65+ // start reticketing transactions on mount
66+ useLifecycle(() => {
67+ (async () => {
68+ const point = need.point(pointCursor);
69+ const details = need.details(getDetails(point));
70+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
71+ try {
72+ await reticketPointBetweenWallets({
73+ fromWallet: need.wallet(wallet),
74+ fromWalletType: walletType,
75+ fromWalletHdPath: walletHdPath,
76+ toWallet: newWallet.value.wallet,
77+ point: point,
78+ web3: need.web3(web3),
79+ contracts: need.contracts(contracts),
80+ networkType,
81+ onUpdate: handleUpdate,
82+ nextRevision: networkRevision + 1,
83+ });
84+ } catch (err) {
85+ console.error(err);
86+ setGeneralError(err);
87+ }
88+ })();
89+ });
90+
91+ const goToRestart = useCallback(() => {
92+ reset();
93+ setNewWallet(Nothing());
94+ }, [reset, setNewWallet]);
95+
96+ const loginAndGoHome = useCallback(() => {
97+ // set wallet state
98+ resetWallet();
99+ setWalletType(WALLET_TYPES.TICKET);
100+ setUrbitWallet(Just(newWallet.value.wallet));
101+ // (implicit) pointCursor stays the same
102+ // rediect to point
103+ popTo(names.POINT);
104+ }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
105+
106+ const handleUpdate = useCallback(
107+ ({ type, state, value }) => {
108+ switch (type) {
109+ case 'progress':
110+ return setProgress(state);
111+ case 'askFunding':
112+ return setNeedFunds(value);
113+ case 'gotFunding':
114+ return setNeedFunds(false);
115+ default:
116+ console.error(`Unknown update: ${type}`);
117+ }
118+ },
119+ [setProgress, setNeedFunds]
120+ );
121+
122+ const renderAdditionalInfo = () => {
123+ if (generalError) {
124+ console.log(generalError);
125+ generalError.message = generalError.message || 'Something went wrong!';
126+ return (
127+ <>
128+ <Grid.Item full className="mt4">
129+ <ErrorText>{generalError.message.toString()}</ErrorText>
130+ </Grid.Item>
131+ <Grid.Item
132+ full
133+ className="mt4"
134+ as={RestartButton}
135+ solid
136+ onClick={goToRestart}>
137+ Restart
138+ </Grid.Item>
139+ </>
140+ );
141+ }
142+
143+ if (needFunds) {
144+ return (
145+ <Grid.Item full as={NeedFundsNotice} className="mt4" {...needFunds} />
146+ );
147+ }
148+
149+ return (
150+ <Grid.Item full as={WarningBox} className="mt4">
151+ Never give your Master Ticket to anyone
152+ </Grid.Item>
153+ );
154+ };
155+
156+ return (
157+ <Grid className="mt4">
158+ <Grid.Item full as={H4}>
159+ {labelForProgress(progress)}
160+ </Grid.Item>
161+
162+ {isDone ? (
163+ <>
164+ <Grid.Item full as={Text} className="mt4">
165+ Your changes are now reflected on-chain and you can use the new
166+ ticket to manage your point.
167+ </Grid.Item>
168+ <Grid.Item
169+ full
170+ as={ForwardButton}
171+ solid
172+ className="mt4"
173+ accessory="↺"
174+ onClick={loginAndGoHome}>
175+ Login with New Master Ticket
176+ </Grid.Item>
177+ </>
178+ ) : (
179+ <>
180+ <Grid.Item full as={Grid} className="mt4" gap={3}>
181+ <Grid.Item full as={LoadingBar} progress={progress} />
182+ <Grid.Item full as={Text} className="f5 green4">
183+ This process can take up to 5 minutes to complete. Don't close
184+ this page until the process is complete.
185+ </Grid.Item>
186+ </Grid.Item>
187+
188+ {renderAdditionalInfo()}
189+ </>
190+ )}
191+ </Grid>
192+ );
193+}
src/views/UrbitID/ResetKeys/ResetVerify.jsnew file+71
@@ -0,0 +1,71 @@
1+import React, { useCallback, useMemo } from 'react';
2+import { Text, Grid, CheckboxInput } from 'indigo-react';
3+
4+import { useLocalRouter } from 'lib/LocalRouter';
5+import { validateExactly } from 'lib/validators';
6+import { isDevelopment } from 'lib/flags';
7+
8+import BridgeForm from 'form/BridgeForm';
9+import { TicketInput } from 'form/Inputs';
10+import { composeValidator, buildPatqValidator } from 'form/validators';
11+import SubmitButton from 'form/SubmitButton';
12+import FormError from 'form/FormError';
13+
14+const STUB_VERIFY_TICKET = isDevelopment;
15+
16+export default function ResetVerify({ newWallet }) {
17+ const { push, names } = useLocalRouter();
18+
19+ const ticket = newWallet.value.wallet.ticket;
20+ const validate = useMemo(
21+ () =>
22+ composeValidator({
23+ ticket: buildPatqValidator([
24+ validateExactly(ticket, 'Does not match expected master ticket.'),
25+ ]),
26+ }),
27+ [ticket]
28+ );
29+
30+ const initialValues = useMemo(
31+ () => ({
32+ ticket: STUB_VERIFY_TICKET ? ticket : undefined,
33+ }),
34+ [ticket]
35+ );
36+
37+ const goExecute = useCallback(() => push(names.EXECUTE), [push, names]);
38+
39+ return (
40+ <Grid className="mt4">
41+ <Grid.Item full as={Text}>
42+ Verify New Master Ticket
43+ </Grid.Item>
44+ <BridgeForm
45+ validate={validate}
46+ afterSubmit={goExecute}
47+ initialValues={initialValues}>
48+ {({ handleSubmit, values }) => (
49+ <>
50+ <Grid.Item
51+ full
52+ as={TicketInput}
53+ className="mt4"
54+ type={values.showTicket ? 'text' : 'password'}
55+ name="ticket"
56+ label="New master ticket"
57+ />
58+
59+ <Grid.Item full as={CheckboxInput} name="showTicket" label="Show" />
60+
61+ <Grid.Item full as={FormError} />
62+
63+ <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
64+ Verify & Reticket
65+ </Grid.Item>
66+ </>
67+ )}
68+ </BridgeForm>
69+ </Grid>
70+ );
71+}
src/views/UrbitOS.js+10−10
@@ -9,6 +9,7 @@ import { LocalRouterProvider } from 'lib/LocalRouter';
9
9
10import View from 'components/View';
10import View from 'components/View';
11import Crumbs from 'components/Crumbs';
11import Crumbs from 'components/Crumbs';
12+import NavHeader from 'components/NavHeader';
12
13
13import UrbitOSHome from './UrbitOS/Home';
14import UrbitOSHome from './UrbitOS/Home';
14import UrbitOSNetworkingKeys from './UrbitOS/NetworkingKeys';
15import UrbitOSNetworkingKeys from './UrbitOS/NetworkingKeys';
@@ -38,17 +39,16 @@ export default function UrbitOS() {
38
39
39 return (
40 return (
40 <View pop={router.pop} inset>
41 <View pop={router.pop} inset>
42+ <NavHeader>
43+ <Crumbs
44+ routes={[
45+ { text: name, action: () => history.pop() },
46+ { text: 'Urbit OS' },
47+ ]}
48+ />
49+ </NavHeader>
41 <LocalRouterProvider value={router}>
50 <LocalRouterProvider value={router}>
42− <Grid className="mb4">
51+ <Grid className="mb4"></Grid>
43− <Grid.Item
44− full
45− as={Crumbs}
46− routes={[
47− { text: name, action: () => history.pop() },
48− { text: 'Urbit OS' },
49− ]}
50− />
51− </Grid>
52 <Route />
52 <Route />
53 </LocalRouterProvider>
53 </LocalRouterProvider>
54 </View>
54 </View>
src/views/UrbitOS/Home.js+58−64
@@ -1,5 +1,6 @@
1import React, { useCallback, useState, useEffect } from 'react';
1import React, { useCallback, useState, useEffect } from 'react';
2+import cn from 'classnames';
2−import { Grid, Button, SelectInput } from 'indigo-react';
3+import { Grid, Button, SelectInput, Flex } from 'indigo-react';
3import * as ob from 'urbit-ob';
4import * as ob from 'urbit-ob';
4import { azimuth } from 'azimuth-js';
5import { azimuth } from 'azimuth-js';
5
6
@@ -10,14 +11,10 @@ import useCurrentPointName from 'lib/useCurrentPointName';
10import useKeyfileGenerator from 'lib/useKeyfileGenerator';
11import useKeyfileGenerator from 'lib/useKeyfileGenerator';
11import * as need from 'lib/need';
12import * as need from 'lib/need';
12
13
13−import {
14− OutButton,
15− ForwardButton,
16− BootUrbitOSButton,
17− GenerateButton,
18−} from 'components/Buttons';
14+import { OutButton, ForwardButton } from 'components/Buttons';
19import CopyButton from 'components/CopyButton';
15import CopyButton from 'components/CopyButton';
20import NetworkingKeys from 'components/NetworkingKeys';
16import NetworkingKeys from 'components/NetworkingKeys';
17+import ProgressButton from 'components/ProgressButton';
21
18
22import BridgeForm from 'form/BridgeForm';
19import BridgeForm from 'form/BridgeForm';
23import { useLocalRouter } from 'lib/LocalRouter';
20import { useLocalRouter } from 'lib/LocalRouter';
@@ -97,9 +94,27 @@ export default function UrbitOSHome() {
97function Hosting() {
94function Hosting() {
98 const bind = useKeyfileGenerator();
95 const bind = useKeyfileGenerator();
99 const { keyfile, code, notice } = bind;
96 const { keyfile, code, notice } = bind;
100− const ship = { connected: true }; //useHosting();
97+ const ship = useHosting();
101−
98+
102− const { syncStatus } = ship;
99+ const {
100+ syncStatus,
101+ url,
102+ unknown,
103+ running,
104+ sysEvents,
105+ runEvents,
106+ newEvents,
107+ bootProgress,
108+ bootMessage,
109+ } = ship;
110+
111+ const [synced, setSynced] = useState(false);
112+ useEffect(() => {
113+ if (unknown && !synced) {
114+ syncStatus();
115+ setSynced(true);
116+ }
117+ }, [syncStatus, unknown, setSynced, synced]);
103
118
104 const createShip = useCallback(() => ship.create(keyfile), [keyfile, ship]);
119 const createShip = useCallback(() => ship.create(keyfile), [keyfile, ship]);
105
120
@@ -109,10 +124,10 @@ function Hosting() {
109 const props = { name, options, ship };
124 const props = { name, options, ship };
110
125
111 const renderMain = useCallback(() => {
126 const renderMain = useCallback(() => {
112− if (ship.connected) {
127+ if (ship.running) {
113 return (
128 return (
114 <>
129 <>
115− <Grid.Item cols={[1, 9]} as={OutButton} solid success>
130+ <Grid.Item cols={[1, 9]} as={OutButton} solid success href={url}>
116 Open OS
131 Open OS
117 </Grid.Item>
132 </Grid.Item>
118 <Grid.Item cols={[9, 13]} as={Button} className="b-black b1" center>
133 <Grid.Item cols={[9, 13]} as={Button} className="b-black b1" center>
@@ -121,47 +136,52 @@ function Hosting() {
121 </>
136 </>
122 );
137 );
123 }
138 }
124− if (ship.missing || ship.booting) {
139+ if (ship.missing) {
125 return (
140 return (
126 <Grid.Item
141 <Grid.Item
127 full
142 full
128− as={GenerateButton}
143+ as={ForwardButton}
129 solid
144 solid
130− success
131− disabled={ship.booting || !keyfile}
145+ disabled={!keyfile}
132− loading={ship.booting}
133 onClick={createShip}>
146 onClick={createShip}>
134− Boot Urbit OS
147+ Connect
135 </Grid.Item>
148 </Grid.Item>
136 );
149 );
137 }
150 }
138− if (ship.running || ship.connecting) {
151+ if (ship.pending) {
139− return <HostingDisconnected {...props} />;
152+ return (
153+ <Grid.Item
154+ full
155+ as={ProgressButton}
156+ success
157+ disabled
158+ progress={bootProgress}>
159+ {bootMessage || 'Connecting'}
160+ </Grid.Item>
161+ );
140 }
162 }
141 }, [
163 }, [
142− ship.connected,
143 ship.missing,
164 ship.missing,
144− ship.booting,
145 ship.running,
165 ship.running,
146− ship.connecting,
166+ ship.pending,
167+ bootProgress,
168+ bootMessage,
147 keyfile,
169 keyfile,
148 createShip,
170 createShip,
149− props,
171+ url,
150 ]);
172 ]);
151
173
152 const renderDetails = useCallback(() => {
174 const renderDetails = useCallback(() => {
153− if (ship.connected) {
175+ if (ship.running) {
154 return (
176 return (
155 <>
177 <>
156− <Grid.Item full>Status: Connected</Grid.Item>
157 <Grid.Item cols={[1, 9]} className="gray4">
178 <Grid.Item cols={[1, 9]} className="gray4">
158− <span className="mono">{name}</span> is connected to Tlon and has
179+ <span className="mono">{name}</span> is connected to Tlon
159− the IP address 127.0.0.1
160 </Grid.Item>
180 </Grid.Item>
161 </>
181 </>
162 );
182 );
163 }
183 }
164− if (ship.running || ship.connected) {
184+ if (ship.missing) {
165 return (
185 return (
166 <>
186 <>
167 <Grid.Item full className="gray4">
187 <Grid.Item full className="gray4">
@@ -169,10 +189,6 @@ function Hosting() {
169 <br />
189 <br />
170 <span className="f6">Last connected: 4m ago</span>
190 <span className="f6">Last connected: 4m ago</span>
171 </Grid.Item>
191 </Grid.Item>
172−
173− <Grid.Item full as={ForwardButton} solid>
174− Connect
175− </Grid.Item>
176 </>
192 </>
177 );
193 );
178 }
194 }
@@ -181,8 +197,15 @@ function Hosting() {
181 return (
197 return (
182 <>
198 <>
183 <Grid gap={4}>
199 <Grid gap={4}>
184− <Grid.Item full className="f5">
200+ <Grid.Item full className="f5" as={Flex}>
185− Urbit OS
201+ <Flex.Item>Urbit OS </Flex.Item>
202+ <Flex.Item
203+ className={cn(
204+ { green3: ship.running, grey4: !ship.running },
205+ 'ml3'
206+ )}>
207+ {ship.running ? 'Connected' : 'Disconnected'}
208+ </Flex.Item>
186 </Grid.Item>
209 </Grid.Item>
187 <BridgeForm initialValues={{ provider: 'tlon' }}>
210 <BridgeForm initialValues={{ provider: 'tlon' }}>
188 {() => (
211 {() => (
@@ -224,32 +247,3 @@ function Hosting() {
224 </>
247 </>
225 );
248 );
226}
249}
227−
228−function HostingDisconnected({ options }) {
229− return (
230− <BridgeForm initialValues={{ provider: 'tlon' }}>
231− {() => (
232− <>
233− <Grid.Item full className="gray4">
234− <span className="gray3">Status:</span> Disconnected
235− <br />
236− <span className="f6">Last connected: 4m ago</span>
237− </Grid.Item>
238−
239− <Grid.Item full as={ForwardButton} solid>
240− Connect
241− </Grid.Item>
242−
243− <Grid.Item
244− full
245− as={SelectInput}
246− name="provider"
247− label="Host Provider"
248− options={options}
249− disabled
250− />
251− </>
252− )}
253− </BridgeForm>
254− );
255−}