on master help sign in
bring inline with designs
ee5bed0Liam Fitzgerald
src/Bridge.js+1−1
@@ -20,7 +20,7 @@ import WithErrorBoundary from 'components/WithErrorBoundary';
20import GlobalErrorBoundary from 'components/GlobalErrorBoundary';
20import GlobalErrorBoundary from 'components/GlobalErrorBoundary';
21
21
22const INITIAL_NETWORK_TYPE = isDevelopment
22const INITIAL_NETWORK_TYPE = isDevelopment
23− ? NETWORK_TYPES.ROPSTEN
23+ ? NETWORK_TYPES.LOCAL
24 : NETWORK_TYPES.MAINNET;
24 : NETWORK_TYPES.MAINNET;
25
25
26// NB(shrugs): modify these variables to change the default local state.
26// NB(shrugs): modify these variables to change the default local state.
src/lib/proxy.js+4−4
@@ -23,13 +23,13 @@ export const proxyTypeToHuman = proxyType => {
23export const proxyTypeToHumanDescription = proxyType => {
23export const proxyTypeToHumanDescription = proxyType => {
24 switch (proxyType) {
24 switch (proxyType) {
25 case PROXY_TYPE.MANAGEMENT:
25 case PROXY_TYPE.MANAGEMENT:
26− return 'The management proxy is allowed to manage networking keys.';
26+ return 'Your management key can configure networking settings (network keys and sponsorship)';
27 case PROXY_TYPE.SPAWN:
27 case PROXY_TYPE.SPAWN:
28− return 'The spawn proxy is allowed to spawn points.';
28+ return 'Your spawn key can spawn points.';
29 case PROXY_TYPE.TRANSFER:
29 case PROXY_TYPE.TRANSFER:
30− return 'The transfer proxy is allowed to transfer this point.';
30+ return 'Your transfer key can transfer this point.';
31 case PROXY_TYPE.VOTING:
31 case PROXY_TYPE.VOTING:
32− return "The voting proxy is allowed to vote on this point's behalf.";
32+ return "Your voting key can vote on this point's behalf.";
33 default:
33 default:
34 throw new Error(`Unknown proxyType: ${proxyType}`);
34 throw new Error(`Unknown proxyType: ${proxyType}`);
35 }
35 }
src/lib/routeNames.js+2
@@ -23,4 +23,6 @@ export const ROUTE_NAMES = {
23 STAR_RELEASE: Symbol('STAR_RELEASE'),
23 STAR_RELEASE: Symbol('STAR_RELEASE'),
24 URBIT_OS: Symbol('URBIT_OS'),
24 URBIT_OS: Symbol('URBIT_OS'),
25 HOSTING: Symbol('HOSTING'),
25 HOSTING: Symbol('HOSTING'),
26+ URBIT_ID: Symbol('URBIT_ID'),
27+ RESIDENTS: Symbol('RESIDENTS'),
26};
28};
src/lib/router.js+4−4
@@ -5,7 +5,6 @@ import Senate from 'views/Senate';
5import CreateGalaxy from 'views/CreateGalaxy';
5import CreateGalaxy from 'views/CreateGalaxy';
6import IssueChild from 'views/IssueChild';
6import IssueChild from 'views/IssueChild';
7import StarRelease from 'views/Release';
7import StarRelease from 'views/Release';
8−import SigilGenerator from 'views/SigilGenerator';
9
8
10import Point from 'views/Point';
9import Point from 'views/Point';
11import Points from 'views/Points';
10import Points from 'views/Points';
@@ -16,8 +15,8 @@ import InviteCohort from 'views/Invite/Cohort';
16import AcceptTransfer from 'views/AcceptTransfer';
15import AcceptTransfer from 'views/AcceptTransfer';
17import CancelTransfer from 'views/CancelTransfer';
16import CancelTransfer from 'views/CancelTransfer';
18import UrbitOS from 'views/UrbitOS';
17import UrbitOS from 'views/UrbitOS';
19−
18+import UrbitID from 'views/UrbitID';
20−import Hosting from 'views/Hosting';
19+import Residents from 'views/Residents';
21
20
22import { ROUTE_NAMES } from './routeNames';
21import { ROUTE_NAMES } from './routeNames';
23
22
@@ -38,8 +37,9 @@ export const ROUTES = {
38 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
37 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
39 [ROUTE_NAMES.INVITE_COHORT]: InviteCohort,
38 [ROUTE_NAMES.INVITE_COHORT]: InviteCohort,
40 [ROUTE_NAMES.STAR_RELEASE]: StarRelease,
39 [ROUTE_NAMES.STAR_RELEASE]: StarRelease,
41− [ROUTE_NAMES.SIGIL_GENERATOR]: SigilGenerator,
42 [ROUTE_NAMES.URBIT_OS]: UrbitOS,
40 [ROUTE_NAMES.URBIT_OS]: UrbitOS,
43 [ROUTE_NAMES.BOOT]: Boot,
41 [ROUTE_NAMES.BOOT]: Boot,
44 [ROUTE_NAMES.HOSTING]: Hosting,
42 [ROUTE_NAMES.HOSTING]: Hosting,
43+ [ROUTE_NAMES.URBIT_ID]: UrbitID,
44+ [ROUTE_NAMES.RESIDENTS]: Residents,
45};
45};
src/lib/useResidents.jsnew file+54
@@ -0,0 +1,54 @@
1+import { useState, useCallback, useEffect } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
3+
4+import { azimuth } from 'azimuth-js';
5+
6+import { useNetwork } from 'store/network';
7+
8+import useCurrentPermissions from 'lib/useCurrentPermissions';
9+import * as need from 'lib/need';
10+
11+export function useResidents(point) {
12+ const [residents, setResidents] = useState(Nothing());
13+ const [residentCount, setResidentCount] = useState(Nothing());
14+
15+ const { isParent } = useCurrentPermissions();
16+ const { contracts } = useNetwork();
17+ const _contracts = need.contracts(contracts);
18+
19+ const syncResidentCount = useCallback(async () => {
20+ if (!isParent) {
21+ return;
22+ }
23+ const count = await azimuth.getSponsoringCount(_contracts, point);
24+ setResidentCount(Just(count.toNumber()));
25+ }, [isParent, setResidentCount, point, _contracts]);
26+
27+ const syncResidents = useCallback(async () => {
28+ if (Just.hasInstance(residents) || !isParent) {
29+ return;
30+ }
31+ // Galaxies sponsor themselves
32+ const children = (await azimuth.getSponsoring(_contracts, point)).filter(
33+ p => p !== point
34+ );
35+
36+ setResidents(Just(children));
37+ }, [residents, isParent, setResidents, point, _contracts]);
38+
39+ const [requests, setRequests] = useState(Nothing);
40+ const syncRequests = useCallback(async () => {
41+ const reqs = await azimuth.getEscapeRequests(_contracts, point);
42+ setRequests(Just(reqs));
43+ }, [_contracts, point]);
44+
45+ useEffect(() => {
46+ syncResidentCount();
47+ }, [syncResidentCount]);
48+
49+ // useEffect(() => {
50+ // syncRequests();
51+ // });
52+
53+ return { residents, residentCount, syncResidents, requests, syncRequests };
54+}
src/store/lib/useHostingStore.js+3−3
@@ -104,9 +104,9 @@ function useHostingStore(url) {
104 }
104 }
105 }, [client, pointCursor, resetEvents, setError, setState]);
105 }, [client, pointCursor, resetEvents, setError, setState]);
106
106
107− useEffect(() => {
107+ // useEffect(() => {
108− syncStatus();
108+ // syncStatus();
109− }, [pointCursor, syncStatus]);
109+ // }, [pointCursor, syncStatus]);
110
110
111 const getEvents = useCallback(() => {
111 const getEvents = useCallback(() => {
112 try {
112 try {
src/views/Admin.js−3
@@ -12,7 +12,6 @@ import Crumbs from 'components/Crumbs';
12
12
13import AdminHome from './Admin/AdminHome';
13import AdminHome from './Admin/AdminHome';
14import AdminEditPermissions from './Admin/AdminEditPermissions';
14import AdminEditPermissions from './Admin/AdminEditPermissions';
15−import AdminRedownload from './Admin/AdminRedownload';
16import AdminReticket from './Admin/AdminReticket';
15import AdminReticket from './Admin/AdminReticket';
17import AdminSetProxy from './Admin/AdminSetProxy';
16import AdminSetProxy from './Admin/AdminSetProxy';
18import AdminTransfer from './Admin/AdminTransfer';
17import AdminTransfer from './Admin/AdminTransfer';
@@ -22,7 +21,6 @@ import AdminNetworkingKeys from './Admin/AdminNetworkingKeys';
22const NAMES = {
21const NAMES = {
23 HOME: 'HOME',
22 HOME: 'HOME',
24 EDIT_PERMISSIONS: 'EDIT_PERMISSIONS',
23 EDIT_PERMISSIONS: 'EDIT_PERMISSIONS',
25− REDOWNLOAD: 'REDOWNLOAD',
26 RETICKET: 'RETICKET',
24 RETICKET: 'RETICKET',
27 SET_PROXY: 'SET_PROXY',
25 SET_PROXY: 'SET_PROXY',
28 TRANSFER: 'TRANSFER',
26 TRANSFER: 'TRANSFER',
@@ -33,7 +31,6 @@ const NAMES = {
33const VIEWS = {
31const VIEWS = {
34 [NAMES.HOME]: AdminHome,
32 [NAMES.HOME]: AdminHome,
35 [NAMES.EDIT_PERMISSIONS]: AdminEditPermissions,
33 [NAMES.EDIT_PERMISSIONS]: AdminEditPermissions,
36− [NAMES.REDOWNLOAD]: AdminRedownload,
37 [NAMES.RETICKET]: AdminReticket,
34 [NAMES.RETICKET]: AdminReticket,
38 [NAMES.SET_PROXY]: AdminSetProxy,
35 [NAMES.SET_PROXY]: AdminSetProxy,
39 [NAMES.TRANSFER]: AdminTransfer,
36 [NAMES.TRANSFER]: AdminTransfer,
src/views/Point.js+101−78
@@ -1,4 +1,4 @@
1−import React, { useCallback, useState } from 'react';
1+import React, { useCallback, useState, useEffect } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Just } from 'folktale/maybe';
3import { Just } from 'folktale/maybe';
4import { Grid, Flex, Button } from 'indigo-react';
4import { Grid, Flex, Button } from 'indigo-react';
@@ -10,7 +10,8 @@ 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';
13−import Blinky from 'components/Blinky';
14+import Blinky, { matchBlinky } from 'components/Blinky';
14import BarGraph from 'components/BarGraph';
15import BarGraph from 'components/BarGraph';
15import Chip from 'components/Chip';
16import Chip from 'components/Chip';
16import InviteSigilList from 'components/InviteSigilList';
17import InviteSigilList from 'components/InviteSigilList';
@@ -24,6 +25,7 @@ import useInvites from 'lib/useInvites';
24import { useSyncOwnedPoints } from 'lib/useSyncPoints';
25import { useSyncOwnedPoints } from 'lib/useSyncPoints';
25import useCurrentPermissions from 'lib/useCurrentPermissions';
26import useCurrentPermissions from 'lib/useCurrentPermissions';
26import { useLocalRouter } from 'lib/LocalRouter';
27import { useLocalRouter } from 'lib/LocalRouter';
28+import { useResidents } from 'lib/useResidents';
27
29
28import Inviter from 'views/Invite/Inviter';
30import Inviter from 'views/Invite/Inviter';
29
31
@@ -34,6 +36,8 @@ export default function Point() {
34 const { wallet } = useWallet();
36 const { wallet } = useWallet();
35 const point = need.point(pointCursor);
37 const point = need.point(pointCursor);
36
38
39+ const { residentCount } = useResidents(point);
40+
37 const {
41 const {
38 isParent,
42 isParent,
39 isActiveOwner,
43 isActiveOwner,
@@ -76,6 +80,10 @@ export default function Point() {
76
80
77 const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
81 const goUrbitOS = useCallback(() => push(names.URBIT_OS), [push, names]);
78
82
83+ const goUrbitID = useCallback(() => push(names.URBIT_ID), [push, names]);
84+
85+ const goResidents = useCallback(() => push(names.RESIDENTS), [push, names]);
86+
79 const goPartiesSetPoolSize = useCallback(
87 const goPartiesSetPoolSize = useCallback(
80 () => push(names.PARTY_SET_POOL_SIZE),
88 () => push(names.PARTY_SET_POOL_SIZE),
81 [push, names]
89 [push, names]
@@ -135,6 +143,79 @@ export default function Point() {
135
143
136 const address = need.addressFromWallet(wallet);
144 const address = need.addressFromWallet(wallet);
137
145
146+ const InviteForm = useCallback(() => {
147+ return (
148+ <>
149+ <Grid.Item cols={[1, 11]}>
150+ Invite Group
151+ <br />
152+ </Grid.Item>
153+
154+ <Grid.Item
155+ className={cn('t-right underline pointer-hover', {
156+ gray4: sentInvites.getOrElse(0) === 0,
157+ })}
158+ onClick={goCohort}
159+ cols={[11, 13]}>
160+ View
161+ </Grid.Item>
162+ <Grid.Item full>
163+ <Flex align="center">
164+ <Flex.Item>
165+ {acceptedInvites.getOrElse(0)} / {_totalInvites}
166+ </Flex.Item>
167+ {_pendingInvites > 0 && (
168+ <Flex.Item as={Chip} className="bg-yellow1 yellow4">
169+ {_pendingInvites} pending
170+ </Flex.Item>
171+ )}
172+ </Flex>
173+ </Grid.Item>
174+
175+ {showInvites && (
176+ <>
177+ <Grid.Item
178+ full
179+ as={BarGraph}
180+ available={availableInvites}
181+ sent={sentInvites}
182+ accepted={acceptedInvites}
183+ />
184+ <Grid.Item
185+ full
186+ as={InviteSigilList}
187+ pendingPoints={pendingPoints}
188+ acceptedPoints={acceptedPoints}
189+ />
190+ </>
191+ )}
192+ {!showInvites && (
193+ <>
194+ <Grid.Item full className="b-gray4 b-dotted b1 self-center">
195+ <div className="p4 pv8 t-center gray4">
196+ Start your invite group by adding members
197+ </div>
198+ </Grid.Item>
199+ </>
200+ )}
201+ {!showInviteForm && availableInvites.getOrElse(0) > 0 && (
202+ <Grid.Item
203+ full
204+ solid
205+ as={Button}
206+ center
207+ onClick={() => setShowInviteForm(true)}>
208+ Add Members
209+ </Grid.Item>
210+ )}
211+ {showInviteForm && <Inviter />}
212+ <Grid.Item full className="mb2" />
213+ </>
214+ );
215+ });
216+
217+ console.log(residentCount);
218+
138 return (
219 return (
139 <View pop={pop} inset>
220 <View pop={pop} inset>
140 <Greeting point={point} />
221 <Greeting point={point} />
@@ -144,74 +225,16 @@ export default function Point() {
144 animationMode={'slide'}
225 animationMode={'slide'}
145 />
226 />
146 <Grid gap={3}>
227 <Grid gap={3}>
147− {isPlanet && hasInvites && (
228+ {isParent && (
148− <>
229+ <Grid.Item full as={Flex} justify="between" onClick={goResidents}>
149− <Grid.Item cols={[1, 11]}>
150− Invite Group
151− <br />
152− </Grid.Item>
153−
154− <Grid.Item
230+ <Flex.Item>
155− className={cn('t-right underline pointer-hover', {
231+ Residents{' '}
156− gray4: sentInvites.getOrElse(0) === 0,
232+ <span className="gray3">{matchBlinky(residentCount)}</span>
157− })}
158− onClick={goCohort}
159− cols={[11, 13]}>
160− View
161− </Grid.Item>
233+ </Flex.Item>
162− <Grid.Item full>
163− <Flex align="center">
164− <Flex.Item>
165− {acceptedInvites.getOrElse(0)} / {_totalInvites}
166− </Flex.Item>
167− {_pendingInvites > 0 && (
168− <Flex.Item as={Chip} className="bg-yellow1 yellow4">
169− {_pendingInvites} pending
170− </Flex.Item>
234+ <Flex.Item>N/A</Flex.Item>
171− )}
172− </Flex>
173− </Grid.Item>
174−
175− {showInvites && (
176− <>
177− <Grid.Item
178− full
179− as={BarGraph}
180− available={availableInvites}
181− sent={sentInvites}
182− accepted={acceptedInvites}
183− />
184− <Grid.Item
185− full
186− as={InviteSigilList}
187− pendingPoints={pendingPoints}
188− acceptedPoints={acceptedPoints}
189− />
190− </>
191− )}
192− {!showInvites && (
193− <>
194− <Grid.Item full className="b-gray4 b-dotted b1 self-center">
195− <div className="p4 pv8 t-center gray4">
196− Start your invite group by adding members
197− </div>
198− </Grid.Item>
235+ </Grid.Item>
199− </>
200− )}
201− {!showInviteForm && availableInvites.getOrElse(0) > 0 && (
202− <Grid.Item
203− full
204− solid
205− as={Button}
206− center
207− onClick={() => setShowInviteForm(true)}>
208− Add Members
209− </Grid.Item>
210− )}
211− {showInviteForm && <Inviter />}
212− <Grid.Item full className="mb2" />
213− </>
214 )}
236 )}
237+ {isPlanet && hasInvites && <InviteForm />}
215 {!loadedInvites && isPlanet && (
238 {!loadedInvites && isPlanet && (
216 <Grid.Item className="mv2" full>
239 <Grid.Item className="mv2" full>
217 Invite Group <Blinky />
240 Invite Group <Blinky />
@@ -219,28 +242,28 @@ export default function Point() {
219 )}
242 )}
220 </Grid>
243 </Grid>
221 <Grid className="pt2">
244 <Grid className="pt2">
222− {inviteButton}
223− <Grid.Item full as={ForwardButton} onClick={goSigil}>
224− Sigil
225− </Grid.Item>
226 <Grid.Divider />
245 <Grid.Divider />
246+ {inviteButton}
227 <Grid.Item
247 <Grid.Item
228 full
248 full
229 as={ForwardButton}
249 as={ForwardButton}
230 disabled={!canManage}
250 disabled={!canManage}
231− onClick={goUrbitOS}>
251+ onClick={goUrbitID}
232− Urbit OS
252+ className="mt2"
253+ detail="Identity and security settings">
254+ ID
233 </Grid.Item>
255 </Grid.Item>
234 <Grid.Divider />
256 <Grid.Divider />
235 <Grid.Item
257 <Grid.Item
236 full
258 full
237 as={ForwardButton}
259 as={ForwardButton}
238 disabled={!canManage}
260 disabled={!canManage}
261+ className="mt2"
262+ detail="Urbit OS Settings"
239− onClick={goAdmin}>
263+ onClick={goUrbitOS}>
240− Admin
264+ OS
241 </Grid.Item>
265 </Grid.Item>
242 <Grid.Divider />
266 <Grid.Divider />
243−
244 {isParent && (
267 {isParent && (
245 <>
268 <>
246 <Grid.Item
269 <Grid.Item
src/views/Residents.jsnew file+164
@@ -0,0 +1,164 @@
1+import React, { useState, useEffect, useCallback } from 'react';
2+import { Grid, H4 } from 'indigo-react';
3+import { Nothing } from 'folktale/maybe';
4+import * as ob from 'urbit-ob';
5+
6+import View from 'components/View';
7+import Tabs from 'components/Tabs';
8+import Crumbs from 'components/Crumbs';
9+import Sigil from 'components/Sigil';
10+
11+import { useLocalRouter } from 'lib/LocalRouter';
12+import useCurrentPointName from 'lib/useCurrentPointName';
13+import { useResidents } from 'lib/useResidents';
14+import * as need from 'lib/need';
15+
16+import { usePointCursor } from 'store/pointCursor';
17+
18+const NAMES = {
19+ REQUESTS: 'REQUESTS',
20+ ALL: 'ALL',
21+};
22+
23+const OPTIONS = [
24+ { text: 'Requests', value: NAMES.REQUESTS },
25+ { text: 'All', value: NAMES.ALL },
26+];
27+
28+const VIEWS = {
29+ [NAMES.REQUESTS]: Requests,
30+ [NAMES.ALL]: AllResidents,
31+};
32+
33+function Requests({ className }) {
34+ const onThing = () => {};
35+ const { pointCursor } = usePointCursor();
36+ const point = need.point(pointCursor);
37+ const { requests, syncRequests } = useResidents(point);
38+ useEffect(() => {
39+ syncRequests();
40+ }, [syncRequests]);
41+
42+ if (Nothing.hasInstance(requests)) {
43+ return (
44+ <Grid className={className}>
45+ <Grid.Item full>Loading...</Grid.Item>
46+ </Grid>
47+ );
48+ }
49+
50+ const _requests = requests.getOrElse([]);
51+
52+ return (
53+ <Grid className={className}>
54+ {_requests.map(point => (
55+ <Resident
56+ key={point}
57+ point={point}
58+ onAccept={onThing}
59+ onDecline={onThing}
60+ />
61+ ))}
62+ </Grid>
63+ );
64+}
65+
66+function Resident({ point, onAccept, onDecline }) {
67+ const patp = ob.patp(point);
68+ const sigilSize = 50;
69+
70+ const isRequest = onAccept && onDecline;
71+
72+ return (
73+ <>
74+ <Grid.Item className="flex-row justify-center align-center" cols={[1, 3]}>
75+ <div
76+ style={{
77+ display: 'inline-block',
78+ height: `${sigilSize}px`,
79+ width: `${sigilSize}px`,
80+ }}>
81+ <Sigil patp={patp} size={25} colors={['#FFFFFF', '#000000']} />
82+ </div>
83+ </Grid.Item>
84+ <Grid.Item className="flex-row align-center" cols={[3, 6]}>
85+ {patp}{' '}
86+ </Grid.Item>
87+ {isRequest && (
88+ <>
89+ <Grid.Item className="flex-row align-center underline" cols={[7, 10]}>
90+ Accept
91+ </Grid.Item>
92+ <Grid.Item
93+ className="flex-row align-center underline"
94+ cols={[10, 13]}>
95+ Decline
96+ </Grid.Item>
97+ </>
98+ )}
99+ <Grid.Divider />
100+ </>
101+ );
102+}
103+
104+function AllResidents({ className }) {
105+ const { pointCursor } = usePointCursor();
106+ const point = need.point(pointCursor);
107+ const { syncResidents, residents } = useResidents(point);
108+ useEffect(() => {
109+ syncResidents();
110+ }, [syncResidents]);
111+
112+ if (Nothing.hasInstance(residents)) {
113+ return (
114+ <Grid className={className}>
115+ <Grid.Item full>Loading...</Grid.Item>
116+ </Grid>
117+ );
118+ }
119+
120+ const _residents = residents.getOrElse([]);
121+
122+ return (
123+ <Grid className={className}>
124+ {_residents.map(point => (
125+ <Resident key={point} point={point} />
126+ ))}
127+ </Grid>
128+ );
129+}
130+
131+export default function Residents() {
132+ const { pop } = useLocalRouter();
133+ const name = useCurrentPointName();
134+
135+ // const { pointCursor } = usePointCursor();
136+
137+ const [currentTab, setCurrentTab] = useState(NAMES.ALL);
138+
139+ const [req, selectRequest] = useState();
140+
141+ return (
142+ <View pop={pop}>
143+ <Grid>
144+ <Grid.Item
145+ full
146+ as={Crumbs}
147+ routes={[{ text: name, action: pop }, { text: 'Residents' }]}
148+ />
149+ <Grid.Item full as={H4} className="mt4">
150+ Residents
151+ </Grid.Item>
152+ <Grid.Item
153+ full
154+ className="mt1"
155+ as={Tabs}
156+ views={VIEWS}
157+ options={OPTIONS}
158+ currentTab={currentTab}
159+ onTabChange={setCurrentTab}
160+ />
161+ </Grid>
162+ </View>
163+ );
164+}
src/views/UrbitID.jsnew file+77
@@ -0,0 +1,77 @@
1+import React from 'react';
2+import { Grid } from 'indigo-react';
3+
4+import { useHistory } from 'store/history';
5+
6+import useCurrentPointName from 'lib/useCurrentPointName';
7+import useRouter from 'lib/useRouter';
8+import { LocalRouterProvider } from 'lib/LocalRouter';
9+
10+import View from 'components/View';
11+import Crumbs from 'components/Crumbs';
12+
13+import UrbitIDHome from './UrbitID/Home';
14+import SigilGenerator from './UrbitID/SigilGenerator';
15+import DownloadKeys from './UrbitID/DownloadKeys';
16+import SetProxy from './UrbitID/SetProxy';
17+
18+const NAMES = {
19+ HOME: 'HOME',
20+ SIGIL_GENERATOR: 'SIGIL_GENERATOR',
21+ DOWNLOAD_KEYS: 'DOWNLOAD_KEYS',
22+ SET_PROXY: 'SET_PROXY',
23+};
24+
25+const VIEWS = {
26+ [NAMES.HOME]: UrbitIDHome,
27+ [NAMES.SIGIL_GENERATOR]: SigilGenerator,
28+ [NAMES.DOWNLOAD_KEYS]: DownloadKeys,
29+ [NAMES.SET_PROXY]: SetProxy,
30+};
31+
32+const humanizeName = name => {
33+ switch (name) {
34+ case NAMES.SIGIL_GENERATOR:
35+ return 'Sigil';
36+ case NAMES.DOWNLOAD_KEYS:
37+ return 'Download Keys';
38+ case NAMES.SET_PROXY:
39+ return 'Set Key';
40+ default:
41+ return undefined;
42+ }
43+};
44+
45+export default function UrbitOS() {
46+ const history = useHistory();
47+ const name = useCurrentPointName();
48+
49+ const { Route, ...router } = useRouter({
50+ names: NAMES,
51+ views: VIEWS,
52+ initialRoutes: [{ key: NAMES.HOME }],
53+ });
54+
55+ const last = router.peek().key;
56+ const homeAction = last === NAMES.HOME ? undefined : () => history.pop();
57+ const lastCrumb = homeAction ? [{ text: humanizeName(last) }] : [];
58+
59+ return (
60+ <View pop={router.pop} inset>
61+ <LocalRouterProvider value={router}>
62+ <Grid className="mb4">
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 />
74+ </LocalRouterProvider>
75+ </View>
76+ );
77+}
src/views/Admin/AdminRedownload.js → src/views/UrbitID/DownloadKeys.jsrenamed

The content is the same.

src/views/UrbitID/Home.jsnew file+146
@@ -0,0 +1,146 @@
1+import React, { useCallback } from 'react';
2+import { Grid, B } from 'indigo-react';
3+import { azimuth } from 'azimuth-js';
4+import { Just } from 'folktale/maybe';
5+
6+import { ForwardButton } from 'components/Buttons';
7+import CopyButton from 'components/CopyButton';
8+
9+import { useLocalRouter } from 'lib/LocalRouter';
10+import { isZeroAddress } from 'lib/wallet';
11+import { PROXY_TYPE, proxyTypeToHuman } from 'lib/proxy';
12+import * as need from 'lib/need';
13+import capitalize from 'lib/capitalize';
14+import { eqAddr } from 'lib/wallet';
15+import useCurrentPermissions from 'lib/useCurrentPermissions';
16+import convertToInt from 'lib/convertToInt';
17+
18+import { usePointCursor } from 'store/pointCursor';
19+import { usePointCache } from 'store/pointCache';
20+import { useNetwork } from 'store/network';
21+import { useWallet } from 'store/wallet';
22+
23+export default function UrbitIDHome() {
24+ const { push, names } = useLocalRouter();
25+ const { pointCursor } = usePointCursor();
26+ const { getDetails } = usePointCache();
27+ const { urbitWallet } = useWallet();
28+
29+ const { contracts } = useNetwork();
30+
31+ const goSigil = useCallback(() => push(names.SIGIL_GENERATOR), [push, names]);
32+
33+ const goDownloadKeys = useCallback(() => push(names.DOWNLOAD_KEYS), [
34+ push,
35+ names,
36+ ]);
37+
38+ const isMasterTicket = Just.hasInstance(urbitWallet);
39+ const _contracts = need.contracts(contracts);
40+ const point = need.point(pointCursor);
41+ const details = need.details(getDetails(point));
42+ const { canManage, isOwner } = useCurrentPermissions();
43+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
44+
45+ const pointSize = azimuth.getPointSize(point);
46+ const isParent = pointSize !== azimuth.PointSize.Planet;
47+ const isSenate = pointSize === azimuth.PointSize.Galaxy;
48+
49+ const goSetProxy = useCallback(
50+ proxyType => push(names.SET_PROXY, { proxyType }),
51+ [push, names]
52+ );
53+
54+ const renderProxyAction = useCallback(
55+ (proxyType, address) => {
56+ const disabled =
57+ !isOwner ||
58+ (proxyType === PROXY_TYPE.SPAWN ? networkRevision === 0 : false);
59+ return (
60+ <>
61+ <Grid.Item
62+ full
63+ as={ForwardButton}
64+ disabled={disabled}
65+ onClick={() => goSetProxy(proxyType)}>
66+ Edit {capitalize(proxyTypeToHuman(proxyType))} Key
67+ </Grid.Item>
68+ </>
69+ );
70+ },
71+ [goSetProxy, isOwner, networkRevision]
72+ );
73+ return (
74+ <Grid>
75+ <Grid.Item full className="mv7 f5">
76+ Urbit ID
77+ </Grid.Item>
78+ <Grid.Divider />
79+ <Grid.Item full as={ForwardButton} onClick={goSigil}>
80+ Sigil
81+ </Grid.Item>
82+ <Grid.Divider />
83+ <Grid.Item
84+ full
85+ as={ForwardButton}
86+ onClick={goDownloadKeys}
87+ disabled={!isMasterTicket}
88+ disabledDetail={
89+ <B className="wrap ws-normal">
90+ · Master Ticket Authentication required.
91+ </B>
92+ }>
93+ Download Keys
94+ </Grid.Item>
95+ <Grid.Divider />
96+ <Grid.Item
97+ full
98+ as={ForwardButton}
99+ onClick={goDownloadKeys}
100+ detail="Reset Master Key and all other keys">
101+ Reset Keys
102+ </Grid.Item>
103+
104+ <Grid.Divider />
105+
106+ <Grid.Item full className="mv7 f5 gray4">
107+ Advanced
108+ </Grid.Item>
109+ <Grid.Divider />
110+ <Grid.Item
111+ full
112+ as={ForwardButton}
113+ accessory={<CopyButton text={details.owner} />}
114+ detail={<div className="mt1 mono black">{details.owner}</div>}
115+ disabled={!details.owner}>
116+ Ownership Address
117+ </Grid.Item>
118+ <Grid.Divider />
119+
120+ {renderProxyAction(PROXY_TYPE.MANAGEMENT, details.managementProxy)}
121+ <Grid.Divider />
122+
123+ {isParent && renderProxyAction(PROXY_TYPE.SPAWN, details.spawnProxy)}
124+
125+ {isParent && networkRevision === 0 && (
126+ <>
127+ <Grid.Item
128+ full
129+ as={ForwardButton}
130+ className="f6"
131+ accessory={<span className="underline pointer">Set Keys</span>}>
132+ Network Keys Required
133+ </Grid.Item>
134+ <Grid.Divider />
135+ </>
136+ )}
137+
138+ {isSenate && (
139+ <>
140+ {renderProxyAction(PROXY_TYPE.VOTING, details.votingProxy)}{' '}
141+ <Grid.Divider />
142+ </>
143+ )}
144+ </Grid>
145+ );
146+}
src/views/UrbitID/SetProxy.jsnew file+233
@@ -0,0 +1,233 @@
1+import React, { useCallback, useMemo } from 'react';
2+import cn from 'classnames';
3+import { Grid, Text, Flex, ToggleInput } from 'indigo-react';
4+import * as azimuth from 'azimuth-js';
5+
6+import { useNetwork } from 'store/network';
7+import { usePointCache } from 'store/pointCache';
8+import { usePointCursor } from 'store/pointCursor';
9+
10+import {
11+ PROXY_TYPE,
12+ proxyTypeToHuman,
13+ proxyTypeToHumanDescription,
14+} from 'lib/proxy';
15+import * as need from 'lib/need';
16+import { useLocalRouter } from 'lib/LocalRouter';
17+import { ETH_ZERO_ADDR, eqAddr, isZeroAddress } from 'lib/wallet';
18+import capitalize from 'lib/capitalize';
19+import useEthereumTransaction from 'lib/useEthereumTransaction';
20+
21+import ViewHeader from 'components/ViewHeader';
22+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
23+import { GAS_LIMITS } from 'lib/constants';
24+import { AddressInput } from 'form/Inputs';
25+import {
26+ composeValidator,
27+ buildCheckboxValidator,
28+ buildAddressValidator,
29+} from 'form/validators';
30+import BridgeForm from 'form/BridgeForm';
31+import FormError from 'form/FormError';
32+import CopiableAddress from 'components/CopiableAddress';
33+
34+const proxyFromDetails = (details, contracts, proxyType) => {
35+ switch (proxyType) {
36+ case PROXY_TYPE.MANAGEMENT:
37+ return details.managementProxy;
38+ case PROXY_TYPE.SPAWN:
39+ return details.spawnProxy;
40+ case PROXY_TYPE.TRANSFER:
41+ return details.transferProxy;
42+ case PROXY_TYPE.VOTING:
43+ if (eqAddr(details.votingProxy, contracts.delegatedSending.address)) {
44+ return `${details.votingProxy} (invites contract)`;
45+ } else {
46+ return details.votingProxy;
47+ }
48+ default:
49+ throw new Error(`Unknown proxyType: ${proxyType}`);
50+ }
51+};
52+
53+function useSetProxy(proxyType) {
54+ const { contracts } = useNetwork();
55+ const { pointCursor } = usePointCursor();
56+ const { syncDetails } = usePointCache();
57+
58+ const _contracts = need.contracts(contracts);
59+ const _point = need.point(pointCursor);
60+
61+ const { construct, ...rest } = useEthereumTransaction(
62+ useCallback(
63+ address => {
64+ const txArgs = [_contracts, _point, address];
65+
66+ switch (proxyType) {
67+ case PROXY_TYPE.MANAGEMENT:
68+ return azimuth.ecliptic.setManagementProxy(...txArgs);
69+ case PROXY_TYPE.SPAWN:
70+ return azimuth.ecliptic.setSpawnProxy(...txArgs);
71+ case PROXY_TYPE.TRANSFER:
72+ return azimuth.ecliptic.setTransferProxy(...txArgs);
73+ case PROXY_TYPE.VOTING:
74+ return azimuth.ecliptic.setVotingProxy(...txArgs);
75+ default:
76+ throw new Error(`Unknown proxyType ${proxyType}`);
77+ }
78+ },
79+ [_contracts, _point, proxyType]
80+ ),
81+ useCallback(() => syncDetails(_point), [_point, syncDetails]),
82+ GAS_LIMITS.SET_PROXY
83+ );
84+
85+ // force-unset
86+ const unset = useCallback(() => {
87+ construct(ETH_ZERO_ADDR);
88+ }, [construct]);
89+
90+ return {
91+ construct,
92+ unset,
93+ ...rest,
94+ };
95+}
96+
97+export default function SetProxy() {
98+ const { data, pop } = useLocalRouter();
99+ const { getDetails } = usePointCache();
100+ const { pointCursor } = usePointCursor();
101+ const { contracts } = useNetwork();
102+
103+ const _point = need.point(pointCursor);
104+ const _details = need.details(getDetails(_point));
105+ const _contracts = need.contracts(contracts);
106+
107+ const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
108+
109+ const {
110+ construct,
111+ unconstruct,
112+ unset,
113+ inputsLocked,
114+ completed,
115+ bind,
116+ } = useSetProxy(data.proxyType);
117+
118+ const validateForm = useCallback((values, errors) => {
119+ if (!values.unset && errors.address) {
120+ return errors;
121+ }
122+ }, []);
123+
124+ const validate = useMemo(
125+ () =>
126+ composeValidator(
127+ {
128+ unset: buildCheckboxValidator(),
129+ address: buildAddressValidator(),
130+ },
131+ validateForm
132+ ),
133+ [validateForm]
134+ );
135+
136+ const onValues = useCallback(
137+ ({ valid, values, form }) => {
138+ if (valid) {
139+ if (values.unset) {
140+ unset();
141+ form.change('address', '');
142+ } else {
143+ construct(values.address);
144+ }
145+ } else {
146+ unconstruct();
147+ }
148+ },
149+ [construct, unconstruct, unset]
150+ );
151+
152+ const initialValues = useMemo(() => ({ unset: false }), []);
153+
154+ const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
155+ const isProxySet = !isZeroAddress(proxyAddress);
156+
157+ const proxyAddressLabel = useMemo(() => `Current ${properProxyType} Key`, [
158+ properProxyType,
159+ ]);
160+
161+ const header = useMemo(() => {
162+ const pfix = completed ? 'New' : 'Edit';
163+ return `${pfix} ${properProxyType} Key`;
164+ }, [completed, properProxyType]);
165+
166+ return (
167+ <Grid>
168+ <Grid.Item full className="f5 mb2">
169+ {header}
170+ </Grid.Item>
171+
172+ {!completed && (
173+ <>
174+ <Grid.Item full as={Text} className="mb4 f5 gray4">
175+ {proxyTypeToHumanDescription(data.proxyType)}
176+ </Grid.Item>
177+ <Grid.Item
178+ full
179+ as={Text}
180+ className={cn('f6', {
181+ green3: completed,
182+ })}>
183+ Current
184+ </Grid.Item>
185+ </>
186+ )}
187+
188+ <BridgeForm
189+ validate={validate}
190+ onValues={onValues}
191+ initialValues={initialValues}>
192+ {({ handleSubmit, values }) => (
193+ <>
194+ <Grid.Item full as={Flex} row justify="between" align="center">
195+ <Flex.Item
196+ flex
197+ as={Text}
198+ className={cn('mono mv3 gray4', {
199+ black: !completed && isProxySet,
200+ gray4: !completed && !isProxySet,
201+ green3: completed,
202+ })}>
203+ {isProxySet ? proxyAddress : 'Unset'}
204+ </Flex.Item>
205+ </Grid.Item>
206+
207+ {completed ? (
208+ <Grid.Item full className="mb4" />
209+ ) : (
210+ <Grid.Item
211+ full
212+ as={AddressInput}
213+ className="mv4"
214+ name="address"
215+ label={`New ${properProxyType} Address`}
216+ disabled={inputsLocked || values.unset}
217+ />
218+ )}
219+
220+ <Grid.Item full as={FormError} />
221+
222+ <Grid.Item
223+ full
224+ as={InlineEthereumTransaction}
225+ {...bind}
226+ onReturn={() => pop()}
227+ />
228+ </>
229+ )}
230+ </BridgeForm>
231+ </Grid>
232+ );
233+}
src/views/SigilGenerator.js → src/views/UrbitID/SigilGenerator.jsrenamed+3−3
@@ -62,7 +62,7 @@ export default function SigilGenerator() {
62 [point, downloadSigil]
62 [point, downloadSigil]
63 );
63 );
64 return (
64 return (
65− <View pop={pop}>
65+ <>
66 <BridgeForm
66 <BridgeForm
67 validate={validate}
67 validate={validate}
68 initialValues={{
68 initialValues={{
@@ -73,7 +73,7 @@ export default function SigilGenerator() {
73 onSubmit={onSubmit}>
73 onSubmit={onSubmit}>
74 {({ handleSubmit, values }) => (
74 {({ handleSubmit, values }) => (
75 <Grid gap={6}>
75 <Grid gap={6}>
76− <Grid.Item full className="f7">
76+ <Grid.Item full className="f5">
77 Sigil
77 Sigil
78 </Grid.Item>
78 </Grid.Item>
79 <Grid.Item fourth={1}>
79 <Grid.Item fourth={1}>
@@ -121,6 +121,6 @@ export default function SigilGenerator() {
121 </BridgeForm>
121 </BridgeForm>
122
122
123 <canvas style={{ display: 'none' }} ref={canvasRef} />
123 <canvas style={{ display: 'none' }} ref={canvasRef} />
124− </View>
124+ </>
125 );
125 );
126}
126}