on master help sign in
checkpoint: reconfigure admin routes
1c7aa3bMatt Condon (shrugs)
src/Bridge.js+6−1
@@ -23,7 +23,12 @@ const INITIAL_NETWORK_TYPE = isDevelopment
23const SHOULD_STUB_LOCAL = process.env.REACT_APP_STUB_LOCAL === 'true';
23const SHOULD_STUB_LOCAL = process.env.REACT_APP_STUB_LOCAL === 'true';
24const IS_STUBBED = isDevelopment && SHOULD_STUB_LOCAL;
24const IS_STUBBED = isDevelopment && SHOULD_STUB_LOCAL;
25const INITIAL_ROUTES = IS_STUBBED
25const INITIAL_ROUTES = IS_STUBBED
26+ ? [
26− ? [{ key: ROUTE_NAMES.LANDING }, { key: ROUTE_NAMES.LOGIN }]
27+ { key: ROUTE_NAMES.LANDING },
28+ { key: ROUTE_NAMES.LOGIN },
29+ { key: ROUTE_NAMES.POINTS },
30+ { key: ROUTE_NAMES.POINT },
31+ ]
27 : [{ key: ROUTE_NAMES.LANDING }];
32 : [{ key: ROUTE_NAMES.LANDING }];
28
33
29const INITIAL_WALLET = IS_STUBBED
34const INITIAL_WALLET = IS_STUBBED
src/components/Crumbs.js+1−1
@@ -20,7 +20,7 @@ export default function Crumbs({ className, routes = [] }) {
20 {route.text}
20 {route.text}
21 </Flex.Item>
21 </Flex.Item>
22 {i !== lastIndex && (
22 {i !== lastIndex && (
23− <Flex.Item as={Breadcrumb} className={cn(textStyle, 'mh4')}>
23+ <Flex.Item as={Breadcrumb} className={cn(textStyle, 'mh2')}>
24 /
24 /
25 </Flex.Item>
25 </Flex.Item>
26 )}
26 )}
src/lib/router.js−6
@@ -2,9 +2,6 @@ import Landing from '../views/Landing';
2import Login from '../views/Login.js';
2import Login from '../views/Login.js';
3import InvitesManage from '../views/InvitesManage.js';
3import InvitesManage from '../views/InvitesManage.js';
4import Admin from '../views/Admin';
4import Admin from '../views/Admin';
5−import Redownload from '../views/Redownload';
6−import Reticket from '../views/Reticket';
7−import Permissions from '../views/Permissions';
8import AcceptTransfer from '../views/AcceptTransfer';
5import AcceptTransfer from '../views/AcceptTransfer';
9import CancelTransfer from '../views/CancelTransfer';
6import CancelTransfer from '../views/CancelTransfer';
10import CreateGalaxy from '../views/CreateGalaxy';
7import CreateGalaxy from '../views/CreateGalaxy';
@@ -32,9 +29,6 @@ export const ROUTES = {
32 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
29 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
33 [ROUTE_NAMES.LOGIN]: Login,
30 [ROUTE_NAMES.LOGIN]: Login,
34 [ROUTE_NAMES.ADMIN]: Admin,
31 [ROUTE_NAMES.ADMIN]: Admin,
35− [ROUTE_NAMES.REDOWNLOAD]: Redownload,
36− [ROUTE_NAMES.RETICKET]: Reticket,
37− [ROUTE_NAMES.PERMISSIONS]: Permissions,
38 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
32 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
39 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
33 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
40 [ROUTE_NAMES.VIEW_POINT]: ViewPoint,
34 [ROUTE_NAMES.VIEW_POINT]: ViewPoint,
src/lib/usePermissionsForPoint.js+28−2
@@ -1,11 +1,17 @@
1−import { eqAddr } from './wallet';
1+import { azimuth } from 'azimuth-js';
2+
2import { usePointCache } from 'store/pointCache';
3import { usePointCache } from 'store/pointCache';
3
4
5+import { eqAddr } from './wallet';
6+
4const NULL_PERMISSIONS = {
7const NULL_PERMISSIONS = {
5 isOwner: false,
8 isOwner: false,
6 isTransferProxy: false,
9 isTransferProxy: false,
7 isManagementProxy: false,
10 isManagementProxy: false,
11+ canManage: false,
8 canTransfer: false,
12 canTransfer: false,
13+ canSpawn: false,
14+ canVote: false,
9};
15};
10
16
11/**
17/**
@@ -18,12 +24,32 @@ export default function usePermissionsForPoint(address, point) {
18 return getDetails(point).matchWith({
24 return getDetails(point).matchWith({
19 Nothing: () => NULL_PERMISSIONS,
25 Nothing: () => NULL_PERMISSIONS,
20 Just: ({ value: details }) => {
26 Just: ({ value: details }) => {
27+ const pointSize = azimuth.getPointSize(point);
28+ const isPlanet = pointSize === azimuth.PointSize.Planet;
29+ const isStar = pointSize === azimuth.PointSize.Star;
30+ const isGalaxy = pointSize === azimuth.PointSize.Galaxy;
31+
21 const isOwner = eqAddr(address, details.owner);
32 const isOwner = eqAddr(address, details.owner);
22 const isTransferProxy = eqAddr(address, details.transferProxy);
33 const isTransferProxy = eqAddr(address, details.transferProxy);
23 const isManagementProxy = eqAddr(address, details.managementProxy);
34 const isManagementProxy = eqAddr(address, details.managementProxy);
35+ const isSpawnProxy = eqAddr(address, details.spawnProxy);
36+ const isVotingProxy = eqAddr(address, details.votingProxy);
37+
38+ const canManage = isOwner || isManagementProxy;
24 const canTransfer = isOwner || isTransferProxy;
39 const canTransfer = isOwner || isTransferProxy;
25
40
41+ const canSpawn = (isStar || isGalaxy) && (isOwner || isSpawnProxy);
42+ const canVote = isGalaxy && (isOwner || isVotingProxy);
43+
44+ return {
45+ isOwner,
46+ isTransferProxy,
26− return { isOwner, isTransferProxy, isManagementProxy, canTransfer };
47+ isManagementProxy,
48+ canManage,
49+ canTransfer,
50+ canSpawn,
51+ canVote,
52+ };
27 },
53 },
28 });
54 });
29}
55}
src/lib/useRouter.js+11−6
@@ -71,23 +71,28 @@ export default function useRouter({
71 // Scroll to top of page with each route transition
71 // Scroll to top of page with each route transition
72 useEffect(() => {
72 useEffect(() => {
73 window.scrollTo(0, 0);
73 window.scrollTo(0, 0);
74− }, [routes, primary]);
74+ }, [routes]);
75
75
76 // capture browser pop in primary router
76 // capture browser pop in primary router
77 useEffect(() => {
77 useEffect(() => {
78− if (!primary) {
78+ const oldPopState = window.onpopstate;
79− return;
80− }
81
79
82 window.history.pushState(null, null, null);
80 window.history.pushState(null, null, null);
83
81
84 window.onpopstate = e => {
82 window.onpopstate = e => {
83+ if (size <= 1) {
84+ // if this is the root route, give the event to parent router
85+ return oldPopState(e);
86+ }
87+
85 window.history.pushState(null, null, null);
88 window.history.pushState(null, null, null);
86 pop();
89 pop();
87 };
90 };
88
91
89− // TODO: disposer functon
92+ return () => {
90− }, [pop, primary]);
93+ window.onpopstate = oldPopState;
94+ };
95+ }, [size, pop]);
91
96
92 return {
97 return {
93 Route,
98 Route,
src/views/Admin.js+15−4
@@ -1,21 +1,32 @@
1import React from 'react';
1import React from 'react';
2import { Grid } from 'indigo-react';
2import { Grid } from 'indigo-react';
3
3
4−import View from 'components/View';
5−
6import { useHistory } from 'store/history';
4import { useHistory } from 'store/history';
7−import Crumbs from 'components/Crumbs';
5+
8import useCurrentPointName from 'lib/useCurrentPointName';
6import useCurrentPointName from 'lib/useCurrentPointName';
9import useRouter from 'lib/useRouter';
7import useRouter from 'lib/useRouter';
10−import AdminHome from './Admin/AdminHome';
11import { LocalRouterProvider } from 'lib/LocalRouter';
8import { LocalRouterProvider } from 'lib/LocalRouter';
12
9
10+import View from 'components/View';
11+import Crumbs from 'components/Crumbs';
12+
13+import AdminHome from './Admin/AdminHome';
14+import AdminEditPermissions from './Admin/AdminEditPermissions';
15+import AdminRedownload from './Admin/AdminRedownload';
16+import AdminReticket from './Admin/AdminReticket';
17+
13const NAMES = {
18const NAMES = {
14 HOME: 'HOME',
19 HOME: 'HOME',
20+ EDIT_PERMISSIONS: 'EDIT_PERMISSIONS',
21+ REDOWNLOAD: 'REDOWNLOAD',
22+ RETICKET: 'RETICKET',
15};
23};
16
24
17const VIEWS = {
25const VIEWS = {
18 [NAMES.HOME]: AdminHome,
26 [NAMES.HOME]: AdminHome,
27+ [NAMES.EDIT_PERMISSIONS]: AdminEditPermissions,
28+ [NAMES.REDOWNLOAD]: AdminRedownload,
29+ [NAMES.RETICKET]: AdminReticket,
19};
30};
20
31
21export default function Admin() {
32export default function Admin() {
src/views/Permissions.js → src/views/Admin/AdminEditPermissions.jsrenamed+51−61
@@ -1,16 +1,13 @@
1import React, { useState, useCallback } from 'react';
1import React, { useState, useCallback } 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 { azimuth } from 'azimuth-js';
5import { Grid } from 'indigo-react';
4import { Grid } from 'indigo-react';
6
5
7−import View from 'components/View';
8import { ForwardButton } from 'components/Buttons';
6import { ForwardButton } from 'components/Buttons';
9−import FooterButton from 'components/FooterButton';
10import { matchBlinky } from 'components/Blinky';
7import { matchBlinky } from 'components/Blinky';
11
8
12import { ROUTE_NAMES } from 'lib/routeNames';
9import { ROUTE_NAMES } from 'lib/routeNames';
13−import { eqAddr } from 'lib/wallet';
10+import { ETH_ZERO_ADDR } from 'lib/wallet';
14
11
15import useLifecycle from 'lib/useLifecycle';
12import useLifecycle from 'lib/useLifecycle';
16import { useNetwork } from 'store/network';
13import { useNetwork } from 'store/network';
@@ -18,6 +15,8 @@ import { useHistory } from 'store/history';
18import { useWallet } from 'store/wallet';
15import { useWallet } from 'store/wallet';
19import { usePointCursor } from 'store/pointCursor';
16import { usePointCursor } from 'store/pointCursor';
20import { usePointCache } from 'store/pointCache';
17import { usePointCache } from 'store/pointCache';
18+import ViewHeader from 'components/ViewHeader';
19+import usePermissionsForPoint from 'lib/usePermissionsForPoint';
21
20
22//TODO consolidate with azimuth-js' getActivationBlock
21//TODO consolidate with azimuth-js' getActivationBlock
23const getRekeyDate = async (web3, contracts, point) => {
22const getRekeyDate = async (web3, contracts, point) => {
@@ -35,19 +34,24 @@ const getRekeyDate = async (web3, contracts, point) => {
35 }
34 }
36};
35};
37
36
38−export default function Permissions() {
37+export default function AdminEditPermissions() {
39 const history = useHistory();
38 const history = useHistory();
40 const { web3, contracts } = useNetwork();
39 const { web3, contracts } = useNetwork();
41 const { wallet } = useWallet();
40 const { wallet } = useWallet();
42 const { pointCursor } = usePointCursor();
41 const { pointCursor } = usePointCursor();
43− const { pointCache } = usePointCache();
42+ const { getDetails } = usePointCache();
44
43
45 const [rekeyDate, setRekeyDate] = useState(Nothing());
44 const [rekeyDate, setRekeyDate] = useState(Nothing());
46
45
47 const point = need.point(pointCursor);
46 const point = need.point(pointCursor);
48− const pointSize = azimuth.getPointSize(point);
49 const userAddress = need.wallet(wallet).address;
47 const userAddress = need.wallet(wallet).address;
50
48
49+ const pointDetails = need.details(getDetails(point));
50+ const { isOwner, canManage, canSpawn, canVote } = usePermissionsForPoint(
51+ userAddress,
52+ point
53+ );
54+
51 useLifecycle(() => {
55 useLifecycle(() => {
52 getRekeyDate(need.web3(web3), need.contracts(contracts), point).then(
56 getRekeyDate(need.web3(web3), need.contracts(contracts), point).then(
53 res => {
57 res => {
@@ -76,43 +80,21 @@ export default function Permissions() {
76 history,
80 history,
77 ]);
81 ]);
78
82
79− const pointDetails = need.fromPointCache(pointCache, point);
80−
81− const isOwner = eqAddr(userAddress, pointDetails.owner);
82− const canManage =
83− isOwner || eqAddr(userAddress, pointDetails.managementProxy);
84−
85 const proxyAction = (name, address, onClick) => {
83 const proxyAction = (name, address, onClick) => {
86− if (address === '0x0000000000000000000000000000000000000000')
84+ if (address === ETH_ZERO_ADDR) {
87− address = 'Unset';
85+ address = 'Not yet set';
86+ }
87+
88 return (
88 return (
89 <Grid.Item full>
89 <Grid.Item full>
90 <ForwardButton disabled={!isOwner} onClick={onClick} detail={address}>
90 <ForwardButton disabled={!isOwner} onClick={onClick} detail={address}>
91− {`${name} proxy address`}
91+ {`${name} Proxy Address`}
92 </ForwardButton>
92 </ForwardButton>
93 </Grid.Item>
93 </Grid.Item>
94 );
94 );
95 };
95 };
96
96
97− let spawnProxyAction = null;
97+ const networkKeysDetail =
98− if (pointSize !== azimuth.PointSize.Planet) {
99− spawnProxyAction = proxyAction(
100− 'Spawn',
101− pointDetails.spawnProxy,
102− goSetSpawn
103− );
104− }
105−
106− let votingProxyAction = null;
107− if (pointSize === azimuth.PointSize.Galaxy) {
108− votingProxyAction = proxyAction(
109− 'Voting',
110− pointDetails.votingProxy,
111− goSetVoting
112− );
113− }
114−
115− const netKeysText =
116 pointDetails.keyRevisionNumber === 0 ? (
98 pointDetails.keyRevisionNumber === 0 ? (
117 'Not yet configured'
99 'Not yet configured'
118 ) : (
100 ) : (
@@ -123,31 +105,39 @@ export default function Permissions() {
123 );
105 );
124
106
125 return (
107 return (
126− <View>
108+ <Grid>
127− <Grid className="pt2">
109+ <Grid.Item full as={ViewHeader}>
110+ Permissions
111+ </Grid.Item>
128− <Grid.Item full as={ForwardButton} disabled detail={pointDetails.owner}>
112+ <Grid.Item full as={ForwardButton} detail={pointDetails.owner} disabled>
129− Ownership address
113+ Ownership Address
130− </Grid.Item>
114+ </Grid.Item>
131− {proxyAction(
115+ <Grid.Divider />
132− 'Management',
133− pointDetails.managementProxy,
116+ {proxyAction('Management', pointDetails.managementProxy, goSetManagement)}
134− goSetManagement
117+ <Grid.Divider />
135− )}
118+
136− {spawnProxyAction}
119+ {canSpawn && (
137− {votingProxyAction}
120+ <>
121+ {proxyAction('Spawn', pointDetails.spawnProxy, goSetSpawn)}
122+ <Grid.Divider />
123+ </>
124+ )}
125+
126+ {canVote && (
127+ <>
128+ {proxyAction('Voting', pointDetails.votingProxy, goSetVoting)}
129+ <Grid.Divider />
130+ </>
131+ )}
138− <Grid.Item
132+ <Grid.Item
139− full
133+ full
140− as={ForwardButton}
134+ as={ForwardButton}
141− disabled={!canManage}
135+ disabled={!canManage}
142− onClick={goSetKeys}
136+ onClick={goSetKeys}
143− detail={netKeysText}>
137+ detail={networkKeysDetail}>
144− Network keys
138+ Network keys
145− </Grid.Item>
139+ </Grid.Item>
146− </Grid>
140+ <Grid.Divider />
147−
141+ </Grid>
148− <FooterButton detail="Transfer this identity to a new owner" disabled>
149− Transfer
150− </FooterButton>
151− </View>
152 );
142 );
153}
143}
src/views/Admin/AdminHome.js+50−55
@@ -15,9 +15,11 @@ import usePermissionsForPoint from 'lib/usePermissionsForPoint';
15import FooterButton from 'components/FooterButton';
15import FooterButton from 'components/FooterButton';
16import ViewHeader from 'components/ViewHeader';
16import ViewHeader from 'components/ViewHeader';
17import Blinky from 'components/Blinky';
17import Blinky from 'components/Blinky';
18+import { useLocalRouter } from 'lib/LocalRouter';
18
19
19export default function AdminHome() {
20export default function AdminHome() {
20 const history = useHistory();
21 const history = useHistory();
22+ const { push, names } = useLocalRouter();
21 const { urbitWallet, wallet } = useWallet();
23 const { urbitWallet, wallet } = useWallet();
22 const { pointCursor } = usePointCursor();
24 const { pointCursor } = usePointCursor();
23
25
@@ -31,20 +33,12 @@ export default function AdminHome() {
31 generateAndDownload: generateAndDownloadKeyfile,
33 generateAndDownload: generateAndDownloadKeyfile,
32 } = useKeyfileGenerator(point);
34 } = useKeyfileGenerator(point);
33
35
34− const goRedownload = useCallback(
36+ const goRedownload = useCallback(() => push(names.REDOWNLOAD), [push, names]);
35− () => history.push(history.names.REDOWNLOAD),
36− [history]
37− );
38−
39− const goReticket = useCallback(() => history.push(history.names.RETICKET), [
37+ const goReticket = useCallback(() => push(names.RETICKET), [push, names]);
40− history,
38+ const goEditPermissions = useCallback(() => push(names.EDIT_PERMISSIONS), [
39+ push,
40+ names,
41 ]);
41 ]);
42−
43− const goEditPermissions = useCallback(
44− () => history.push(history.names.PERMISSIONS),
45− [history]
46− );
47−
48 const goTransfer = useCallback(() => history.push(history.names.TRANSFER), [
42 const goTransfer = useCallback(() => history.push(history.names.TRANSFER), [
49 history,
43 history,
50 ]);
44 ]);
@@ -52,54 +46,55 @@ export default function AdminHome() {
52 const canDownloadPassport = Just.hasInstance(urbitWallet);
46 const canDownloadPassport = Just.hasInstance(urbitWallet);
53
47
54 return (
48 return (
49+ <>
55− <Grid>
50+ <Grid>
56− <Grid.Item full as={ViewHeader}>
51+ <Grid.Item full as={ViewHeader}>
57− Admin
52+ Admin
58− </Grid.Item>
59−
60− <Grid.Item
61− full
62− as={ForwardButton}
63− disabled={!canDownloadPassport}
64− onClick={goRedownload}
65− detail="Re-download your paper wallet">
66− Download Passport
67− </Grid.Item>
53+ </Grid.Item>
68− <Grid.Divider />
69− <Grid.Item
70− full
71− as={DownloadButton}
72− disabled={!keyfileAvailable}
73− accessory={generatingKeyfile ? <Blinky /> : undefined}
74− onClick={generateAndDownloadKeyfile}
75− detail="Download your Arvo Keyfile">
76− Download Arvo Keyfile
77− </Grid.Item>
78− <Grid.Divider />
79− <Grid.Item
80− full
81− as={ForwardButton}
82− disabled={!isOwner}
83− onClick={goReticket}
84− detail="Move to brand new wallet, resetting all permissions">
85− Reticket
86− </Grid.Item>
87− <Grid.Divider />
88− <Grid.Item
89− full
90− as={ForwardButton}
91− onClick={goEditPermissions}
92− detail="Proxies, networking keys, etc.">
93− Edit permissions
94− </Grid.Item>
95− <Grid.Divider />
96
54
55+ <Grid.Item
56+ full
57+ as={ForwardButton}
58+ disabled={!canDownloadPassport}
59+ onClick={goRedownload}
60+ detail="Re-download your paper wallet">
61+ Download Passport
62+ </Grid.Item>
63+ <Grid.Divider />
64+ <Grid.Item
65+ full
66+ as={DownloadButton}
67+ disabled={!keyfileAvailable}
68+ accessory={generatingKeyfile ? <Blinky /> : undefined}
69+ onClick={generateAndDownloadKeyfile}
70+ detail="Download your Arvo Keyfile">
71+ Download Arvo Keyfile
72+ </Grid.Item>
73+ <Grid.Divider />
74+ <Grid.Item
75+ full
76+ as={ForwardButton}
77+ disabled={!isOwner}
78+ onClick={goReticket}
79+ detail="Move to brand new wallet, resetting all permissions">
80+ Reticket
81+ </Grid.Item>
82+ <Grid.Divider />
83+ <Grid.Item
84+ full
85+ as={ForwardButton}
86+ onClick={goEditPermissions}
87+ detail="Proxies, networking keys, etc.">
88+ Edit Permissions
89+ </Grid.Item>
90+ <Grid.Divider />
91+ </Grid>
97 <FooterButton
92 <FooterButton
98 detail="Transfer this identity to a new owner"
93 detail="Transfer this identity to a new owner"
99 disabled={!canTransfer}
94 disabled={!canTransfer}
100 onClick={goTransfer}>
95 onClick={goTransfer}>
101 Transfer
96 Transfer
102 </FooterButton>
97 </FooterButton>
103− </Grid>
98+ </>
104 );
99 );
105}
100}
src/views/Redownload.js → src/views/Admin/AdminRedownload.jsrenamed+1−1
@@ -12,7 +12,7 @@ import { downloadWallet } from 'lib/invite';
12
12
13import { useWallet } from 'store/wallet';
13import { useWallet } from 'store/wallet';
14
14
15−export default function Redownload() {
15+export default function AdminRedownload() {
16 const { urbitWallet } = useWallet();
16 const { urbitWallet } = useWallet();
17 const { pointCursor } = usePointCursor();
17 const { pointCursor } = usePointCursor();
18 const point = need.point(pointCursor);
18 const point = need.point(pointCursor);
src/views/Reticket.js → src/views/Admin/AdminReticket.jsrenamed+5−5
@@ -15,10 +15,10 @@ import useRouter from 'lib/useRouter';
15
15
16import { useHistory } from 'store/history';
16import { useHistory } from 'store/history';
17
17
18−import Confirm from './Reticket/Confirm';
18+import Confirm from '../Reticket/Confirm';
19−import Download from './Reticket/Download';
19+import Download from '../Reticket/Download';
20−import Verify from './Reticket/Verify';
20+import Verify from '../Reticket/Verify';
21−import DoReticket from './Reticket/DoReticket';
21+import DoReticket from '../Reticket/DoReticket';
22
22
23const NAMES = {
23const NAMES = {
24 CONFIRM: 'CONFIRM',
24 CONFIRM: 'CONFIRM',
@@ -36,7 +36,7 @@ const VIEWS = {
36
36
37const STEP_MAX = size(NAMES);
37const STEP_MAX = size(NAMES);
38
38
39−export default function Reticket() {
39+export default function AdminReticket() {
40 const history = useHistory();
40 const history = useHistory();
41 const { Route, ...innerRouter } = useRouter({
41 const { Route, ...innerRouter } = useRouter({
42 names: NAMES,
42 names: NAMES,