on master help sign in
feat: update admin views, preparing for ethereum hook/components
af19266Matt Condon (shrugs)
src/components/Buttons.js+6
@@ -17,3 +17,9 @@ export const OfflineButton = props => (
17 Offline
17 Offline
18 </OutButton>
18 </OutButton>
19);
19);
20+
21+export const BootArvoButton = props => (
22+ <ForwardButton detail="Boot your computer" {...props}>
23+ Boot Arvo
24+ </ForwardButton>
25+);
src/components/DownloadKeyfileButton.jsnew file+31
@@ -0,0 +1,31 @@
1+import React from 'react';
2+
3+import { DownloadButton } from 'components/Buttons';
4+import Blinky from 'components/Blinky';
5+
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import useKeyfileGenerator from 'lib/useKeyfileGenerator';
9+import * as need from 'lib/need';
10+
11+export default function DownloadKeyfileButton({ className, ...rest }) {
12+ const { pointCursor } = usePointCursor();
13+ const point = need.point(pointCursor);
14+ const {
15+ generating,
16+ available,
17+ downloaded,
18+ generateAndDownload,
19+ } = useKeyfileGenerator(point);
20+
21+ return (
22+ <DownloadButton
23+ className={className}
24+ disabled={downloaded || !available}
25+ accessory={generating ? <Blinky /> : undefined}
26+ onClick={generateAndDownload}
27+ {...rest}>
28+ Download Arvo Keyfile
29+ </DownloadButton>
30+ );
31+}
src/indigo-react/components/LinkButton.jsnew file+35
@@ -0,0 +1,35 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+export default function LinkButton({
5+ as: As = 'a',
6+ solid = false,
7+ disabled = false,
8+ className,
9+ onClick,
10+ children,
11+ ...rest
12+}) {
13+ return (
14+ <span
15+ onClick={!disabled && onClick ? onClick : undefined}
16+ style={{
17+ ...(disabled && {
18+ pointerEvents: 'none',
19+ cursor: 'not-allowed',
20+ }),
21+ }}
22+ className={cn(
23+ 'pointer ph2 underline',
24+ {
25+ white: solid,
26+ black: !solid && !disabled,
27+ gray4: !solid && disabled,
28+ },
29+ className
30+ )}
31+ {...rest}>
32+ {children}
33+ </span>
34+ );
35+}
src/indigo-react/index.js+1
@@ -7,6 +7,7 @@ export { default as CheckboxInput } from './components/CheckboxInput';
7export { default as AccessoryIcon } from './components/AccessoryIcon';
7export { default as AccessoryIcon } from './components/AccessoryIcon';
8export { default as Button } from './components/Button';
8export { default as Button } from './components/Button';
9export { default as IconButton } from './components/IconButton';
9export { default as IconButton } from './components/IconButton';
10+export { default as LinkButton } from './components/LinkButton';
10
11
11export * from './components/Typography';
12export * from './components/Typography';
12
13
src/lib/dateFormat.js+5
@@ -6,3 +6,8 @@ export const formatDots = date => {
6
6
7 return [year, month, day].join('.');
7 return [year, month, day].join('.');
8};
8};
9+
10+export const formatTime = date => date.toLocaleTimeString();
11+
12+export const formatDotsWithTime = date =>
13+ `${formatDots(date)} ${formatTime(date)}`;
src/lib/keys.js+11−2
@@ -1,12 +1,11 @@
1import BN from 'bn.js';
1import BN from 'bn.js';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3−import * as need from './need';
4
3
5import * as noun from '../nockjs/noun';
4import * as noun from '../nockjs/noun';
6import * as serial from '../nockjs/serial';
5import * as serial from '../nockjs/serial';
7import * as kg from 'urbit-key-generation/dist';
6import * as kg from 'urbit-key-generation/dist';
8
7
9−import { eqAddr, addHexPrefix } from './wallet';
8+import { eqAddr, addHexPrefix, CURVE_ZERO_ADDR } from './wallet';
10
9
11const NETWORK_KEY_CURVE_PARAMETER = '42';
10const NETWORK_KEY_CURVE_PARAMETER = '42';
12
11
@@ -163,3 +162,13 @@ export const keysMatchChain = (pair, details) => {
163 authenticationKey === addHexPrefix(auth.public)
162 authenticationKey === addHexPrefix(auth.public)
164 );
163 );
165};
164};
165+
166+export const segmentNetworkKey = hex => {
167+ const sl = i => hex.slice(i, i + 4);
168+ const rowFrom = i => `${sl(i)}.${sl(i + 4)}.${sl(i + 8)}.${sl(i + 12)}`;
169+ if (hex === CURVE_ZERO_ADDR) {
170+ return null;
171+ }
172+
173+ return [rowFrom(2), rowFrom(18), rowFrom(34), rowFrom(50)];
174+};
src/lib/useInputs.js+6−1
@@ -15,6 +15,7 @@ import {
15 validateOneOf,
15 validateOneOf,
16 validateMaximumPatpByteLength,
16 validateMaximumPatpByteLength,
17 validateEthereumAddress,
17 validateEthereumAddress,
18+ validateNotNullAddress,
18} from 'lib/validators';
19} from 'lib/validators';
19import { prependSig } from 'lib/transformers';
20import { prependSig } from 'lib/transformers';
20
21
@@ -212,7 +213,11 @@ export function useSelectInput({ initialValue, options, ...rest }) {
212 );
213 );
213}
214}
214
215
215−const kAddressValidators = [validateEthereumAddress, validateNotEmpty];
216+const kAddressValidators = [
217+ validateEthereumAddress,
218+ validateNotNullAddress,
219+ validateNotEmpty,
220+];
216export function useAddressInput({ ...rest }) {
221export function useAddressInput({ ...rest }) {
217 return useFirstOf(
222 return useFirstOf(
218 useForm([
223 useForm([
src/lib/useKeyfileGenerator.js+6−10
@@ -16,21 +16,15 @@ import {
16import usePermissionsForPoint from './usePermissionsForPoint';
16import usePermissionsForPoint from './usePermissionsForPoint';
17
17
18export default function useKeyfileGenerator(point) {
18export default function useKeyfileGenerator(point) {
19− const { getDetails, getOptimisticDetails } = usePointCache();
19+ const { getDetails } = usePointCache();
20 const { urbitWallet, wallet, authMnemonic } = useWallet();
20 const { urbitWallet, wallet, authMnemonic } = useWallet();
21
21
22+ const [downloaded, setDownloaded] = useState(false);
22 const [generating, setGenerating] = useState(false);
23 const [generating, setGenerating] = useState(false);
23 const [keyfile, setKeyfile] = useState();
24 const [keyfile, setKeyfile] = useState();
24
25
25 const details = need.details(getDetails(point));
26 const details = need.details(getDetails(point));
26− const optimisticDetails = getOptimisticDetails(point);
27−
28− // use the optimistic key revision number if available
29− // (because we may have set networking keys earlier)
30− const networkRevision = optimisticDetails.keyRevisionNumber.matchWith({
31− Nothing: () => parseInt(details.keyRevisionNumber, 10),
27+ const networkRevision = parseInt(details.keyRevisionNumber, 10);
32− Just: p => p.value,
33− });
34
28
35 const { isOwner, isManagementProxy } = usePermissionsForPoint(point);
29 const { isOwner, isManagementProxy } = usePermissionsForPoint(point);
36 const hasNetworkingKeys = networkRevision > 0;
30 const hasNetworkingKeys = networkRevision > 0;
@@ -82,7 +76,8 @@ export default function useKeyfileGenerator(point) {
82 `${ob.patp(point).slice(1)}-${networkRevision}.key`
76 `${ob.patp(point).slice(1)}-${networkRevision}.key`
83 // TODO: ^ unifiy "remove tilde" calls
77 // TODO: ^ unifiy "remove tilde" calls
84 );
78 );
79+ setDownloaded(true);
85− }, [point, keyfile, networkRevision]);
80+ }, [point, keyfile, networkRevision, setDownloaded]);
86
81
87 const generateAndDownload = useCallback(async () => {
82 const generateAndDownload = useCallback(async () => {
88 await generate();
83 await generate();
@@ -92,6 +87,7 @@ export default function useKeyfileGenerator(point) {
92 return {
87 return {
93 generating,
88 generating,
94 available,
89 available,
90+ downloaded,
95 generateAndDownload,
91 generateAndDownload,
96 };
92 };
97}
93}
src/lib/validators.js+13−1
@@ -2,7 +2,7 @@ import * as bip39 from 'bip39';
2import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
3import { identity, includes } from 'lodash';
3import { identity, includes } from 'lodash';
4
4
5−import { isValidAddress } from './wallet';
5+import { isValidAddress, ETH_ZERO_ADDR, ETH_ZERO_ADDR_SHORT } from './wallet';
6import patp2dec from './patp2dec';
6import patp2dec from './patp2dec';
7import { patpStringLength } from './lib';
7import { patpStringLength } from './lib';
8
8
@@ -204,6 +204,13 @@ export const validateExactly = (value, error) => m =>
204 error,
204 error,
205 });
205 });
206
206
207+export const validateNotEqual = (values = []) => m =>
208+ simpleValidatorWrapper({
209+ prevMessage: m,
210+ validator: d => !values.includes(d),
211+ error: `Cannot be ${m.data}`,
212+ });
213+
207export const validateLength = l => m =>
214export const validateLength = l => m =>
208 simpleValidatorWrapper({
215 simpleValidatorWrapper({
209 prevMessage: m,
216 prevMessage: m,
@@ -237,3 +244,8 @@ export const validatePatpByteLength = byteLength => {
237export const validateMaximumPatpByteLength = byteLength => {
244export const validateMaximumPatpByteLength = byteLength => {
238 return validateMaximumLength(patpStringLength(byteLength));
245 return validateMaximumLength(patpStringLength(byteLength));
239};
246};
247+
248+export const validateNotNullAddress = validateNotEqual(
249+ [ETH_ZERO_ADDR, ETH_ZERO_ADDR_SHORT],
250+ 'Cannot be 0x0'
251+);
src/lib/wallet.js+1−1
@@ -4,12 +4,12 @@ import keccak from 'keccak';
4import { reduce } from 'lodash';
4import { reduce } from 'lodash';
5import { Just, Nothing } from 'folktale/maybe';
5import { Just, Nothing } from 'folktale/maybe';
6import * as secp256k1 from 'secp256k1';
6import * as secp256k1 from 'secp256k1';
7−import * as ob from 'urbit-ob';
8import * as kg from 'urbit-key-generation/dist';
7import * as kg from 'urbit-key-generation/dist';
9import { isAddress } from 'web3-utils';
8import { isAddress } from 'web3-utils';
10
9
11export const DEFAULT_HD_PATH = "m/44'/60'/0'/0/0";
10export const DEFAULT_HD_PATH = "m/44'/60'/0'/0/0";
12export const ETH_ZERO_ADDR = '0x0000000000000000000000000000000000000000';
11export const ETH_ZERO_ADDR = '0x0000000000000000000000000000000000000000';
12+export const ETH_ZERO_ADDR_SHORT = '0x0';
13export const CURVE_ZERO_ADDR =
13export const CURVE_ZERO_ADDR =
14 '0x0000000000000000000000000000000000000000000000000000000000000000';
14 '0x0000000000000000000000000000000000000000000000000000000000000000';
15
15
src/store/lib/useOptimisticDetailsStore.jsdeleted−38
@@ -1,38 +0,0 @@
1−import { useCallback } from 'react';
2−import { Just, Nothing } from 'folktale/maybe';
3−
4−import useSetState from 'lib/useSetState';
5−
6−const DEFAULT_OPTIMISTIC_DETAILS = {
7− keyRevisionNumber: Nothing(),
8−};
9−
10−export default function useOptimisticDetailsStore() {
11− const [optimisticDetails, addToOptimisticDetails] = useSetState({});
12−
13− const getOptimisticDetails = useCallback(
14− point => optimisticDetails[point] || DEFAULT_OPTIMISTIC_DETAILS,
15− [optimisticDetails]
16− );
17−
18− const _buildSetter = key =>
19− useCallback(
20− (point, value) =>
21− addToOptimisticDetails({
22− [point]: {
23− ...DEFAULT_OPTIMISTIC_DETAILS,
24− ...optimisticDetails[point],
25− ...optimisticDetails({ [key]: Just(value) }),
26− },
27− }),
28− // NOTE: this usage of useCallback is ok because it is called a constant
29− // number of times. the linter also doesn't understand that it's within
30− // a closure, so we disable the rule here.
31− // eslint-disable-next-line react-hooks/exhaustive-deps
32− [addToOptimisticDetails, optimisticDetails]
33− );
34−
35− const setOptimisticKeyRevision = _buildSetter('keyRevisionNumber');
36−
37− return { getOptimisticDetails, setOptimisticKeyRevision };
38−}
src/store/lib/usePointStore.js−3
@@ -6,7 +6,6 @@ import useRekeyDateStore from './useRekeyDateStore';
6import useInvitesStore from './useInvitesStore';
6import useInvitesStore from './useInvitesStore';
7import useControlledPointsStore from './useControlledPointsStore';
7import useControlledPointsStore from './useControlledPointsStore';
8import useEclipticOwnerStore from './useEclipticOwnerStore';
8import useEclipticOwnerStore from './useEclipticOwnerStore';
9−import useOptimisticPointDetailsStore from './useOptimisticDetailsStore';
10
9
11export default function usePointStore() {
10export default function usePointStore() {
12 const { syncDetails, ...details } = useDetailsStore();
11 const { syncDetails, ...details } = useDetailsStore();
@@ -18,7 +17,6 @@ export default function usePointStore() {
18 ...controlledPoints
17 ...controlledPoints
19 } = useControlledPointsStore();
18 } = useControlledPointsStore();
20 const ecliptic = useEclipticOwnerStore();
19 const ecliptic = useEclipticOwnerStore();
21− const optimistic = useOptimisticPointDetailsStore();
22
20
23 // sync all of the on-chain info required to display a known point
21 // sync all of the on-chain info required to display a known point
24 const syncKnownPoint = useCallback(
22 const syncKnownPoint = useCallback(
@@ -51,7 +49,6 @@ export default function usePointStore() {
51 ...invites,
49 ...invites,
52 ...controlledPoints,
50 ...controlledPoints,
53 ...ecliptic,
51 ...ecliptic,
54− ...optimistic,
55 syncInvites,
52 syncInvites,
56 syncControlledPoints,
53 syncControlledPoints,
57 syncKnownPoint,
54 syncKnownPoint,
src/store/lib/useRekeyDateStore.js+15−9
@@ -1,5 +1,6 @@
1import { useCallback, useState } from 'react';
1import { useCallback, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3+import Result from 'folktale/result';
3
4
4import { useNetwork } from '../network';
5import { useNetwork } from '../network';
5
6
@@ -16,6 +17,10 @@ export default function useRekeyDateStore() {
16 [_setRekeyDateCache]
17 [_setRekeyDateCache]
17 );
18 );
18
19
20+ /**
21+ * @param {number} point
22+ * @return {Maybe<Result<Date, String>>}
23+ */
19 const getRekeyDate = useCallback(
24 const getRekeyDate = useCallback(
20 point => rekeyDateCache[point] || Nothing(),
25 point => rekeyDateCache[point] || Nothing(),
21 [rekeyDateCache]
26 [rekeyDateCache]
@@ -24,11 +29,8 @@ export default function useRekeyDateStore() {
24 const syncRekeyDate = useCallback(
29 const syncRekeyDate = useCallback(
25 async point => {
30 async point => {
26 const _web3 = web3.getOrElse(null);
31 const _web3 = web3.getOrElse(null);
27− if (!_web3) {
28− return;
29− }
30 const _contracts = contracts.getOrElse(null);
32 const _contracts = contracts.getOrElse(null);
31− if (!_contracts) {
33+ if (!_web3 || !_contracts) {
32 return;
34 return;
33 }
35 }
34
36
@@ -37,26 +39,30 @@ export default function useRekeyDateStore() {
37 const logs = await _contracts.azimuth.getPastEvents('ChangedKeys', {
39 const logs = await _contracts.azimuth.getPastEvents('ChangedKeys', {
38 fromBlock: 0,
40 fromBlock: 0,
39 toBlock: 'latest',
41 toBlock: 'latest',
42+ filter: {
40− filter: { point: [point] },
43+ point: [point],
44+ },
41 });
45 });
42
46
43 if (logs.length === 0) {
47 if (logs.length === 0) {
44 //TODO: better encoding for "no rekeyDate" state?
48 //TODO: better encoding for "no rekeyDate" state?
45 addToRekeyDateCache({
49 addToRekeyDateCache({
46− [point]: Just('unknown'),
50+ [point]: Just(Result.Error('No rekey date available.')),
47 });
51 });
48 } else {
52 } else {
49 // last log is most recent
53 // last log is most recent
50 const blockNumber = logs[logs.length - 1].blockNumber;
54 const blockNumber = logs[logs.length - 1].blockNumber;
51 const block = await _web3.eth.getBlock(blockNumber);
55 const block = await _web3.eth.getBlock(blockNumber);
52 addToRekeyDateCache({
56 addToRekeyDateCache({
53− //TODO date format
54− [point]: Just(new Date(block.timestamp * 1000).toString()),
57+ [point]: Just(Result.Ok(new Date(block.timestamp * 1000))),
55 });
58 });
56 }
59 }
57 },
60 },
58 [web3, contracts, addToRekeyDateCache]
61 [web3, contracts, addToRekeyDateCache]
59 );
62 );
60
63
64+ return {
65+ getRekeyDate,
61− return { getRekeyDate, syncRekeyDate };
66+ syncRekeyDate,
67+ };
62}
68}
src/views/Activate/ActivatePassport.js+4−4
@@ -64,8 +64,8 @@ export default function ActivatePassport() {
64 );
64 );
65
65
66 return (
66 return (
67+ <View full={fullView}>
67− <LocalRouterProvider value={router}>
68+ <LocalRouterProvider value={router}>
68− <View full={fullView}>
69 <Grid gap={gap} className="mt8 mb10">
69 <Grid gap={gap} className="mt8 mb10">
70 <Grid.Item half={half && 1} full={full}>
70 <Grid.Item half={half && 1} full={full}>
71 <Passport
71 <Passport
@@ -82,7 +82,7 @@ export default function ActivatePassport() {
82 resetActivateRouter={resetActivateRouter}
82 resetActivateRouter={resetActivateRouter}
83 />
83 />
84 </Grid>
84 </Grid>
85− </View>
85+ </LocalRouterProvider>
86− </LocalRouterProvider>
86+ </View>
87 );
87 );
88}
88}
src/views/Admin.js+11−5
@@ -15,6 +15,8 @@ import AdminEditPermissions from './Admin/AdminEditPermissions';
15import AdminRedownload from './Admin/AdminRedownload';
15import AdminRedownload from './Admin/AdminRedownload';
16import AdminReticket from './Admin/AdminReticket';
16import AdminReticket from './Admin/AdminReticket';
17import AdminSetProxy from './Admin/AdminSetProxy';
17import AdminSetProxy from './Admin/AdminSetProxy';
18+import AdminTransfer from './Admin/AdminTransfer';
19+import AdminNetworkingKeys from './Admin/AdminNetworkingKeys';
18
20
19const NAMES = {
21const NAMES = {
20 HOME: 'HOME',
22 HOME: 'HOME',
@@ -22,6 +24,8 @@ const NAMES = {
22 REDOWNLOAD: 'REDOWNLOAD',
24 REDOWNLOAD: 'REDOWNLOAD',
23 RETICKET: 'RETICKET',
25 RETICKET: 'RETICKET',
24 SET_PROXY: 'SET_PROXY',
26 SET_PROXY: 'SET_PROXY',
27+ TRANSFER: 'TRANSFER',
28+ NETWORKING_KEYS: 'NETWORKING_KEYS',
25};
29};
26
30
27const VIEWS = {
31const VIEWS = {
@@ -30,6 +34,8 @@ const VIEWS = {
30 [NAMES.REDOWNLOAD]: AdminRedownload,
34 [NAMES.REDOWNLOAD]: AdminRedownload,
31 [NAMES.RETICKET]: AdminReticket,
35 [NAMES.RETICKET]: AdminReticket,
32 [NAMES.SET_PROXY]: AdminSetProxy,
36 [NAMES.SET_PROXY]: AdminSetProxy,
37+ [NAMES.TRANSFER]: AdminTransfer,
38+ [NAMES.NETWORKING_KEYS]: AdminNetworkingKeys,
33};
39};
34
40
35export default function Admin() {
41export default function Admin() {
@@ -43,10 +49,11 @@ export default function Admin() {
43 });
49 });
44
50
45 return (
51 return (
52+ <View inset>
46− <LocalRouterProvider value={router}>
53+ <LocalRouterProvider value={router}>
47− <View inset>
48 <Grid className="mb4">
54 <Grid className="mb4">
49 <Grid.Item
55 <Grid.Item
56+ full
50 as={Crumbs}
57 as={Crumbs}
51 routes={[
58 routes={[
52 {
59 {
@@ -57,11 +64,10 @@ export default function Admin() {
57 text: 'Admin',
64 text: 'Admin',
58 },
65 },
59 ]}
66 ]}
60− full
61 />
67 />
62 </Grid>
68 </Grid>
63 <Route />
69 <Route />
64− </View>
70+ </LocalRouterProvider>
65− </LocalRouterProvider>
71+ </View>
66 );
72 );
67}
73}
src/views/Admin/AdminEditPermissions.js+41−56
@@ -5,7 +5,7 @@ import { Grid } from 'indigo-react';
5import { azimuth } from 'azimuth-js';
5import { azimuth } from 'azimuth-js';
6
6
7import { ForwardButton } from 'components/Buttons';
7import { ForwardButton } from 'components/Buttons';
8−import { matchBlinky, matchBlinkyDate } from 'components/Blinky';
8+import { matchBlinkyDate } from 'components/Blinky';
9
9
10import { ETH_ZERO_ADDR } from 'lib/wallet';
10import { ETH_ZERO_ADDR } from 'lib/wallet';
11
11
@@ -18,38 +18,20 @@ import ViewHeader from 'components/ViewHeader';
18import usePermissionsForPoint from 'lib/usePermissionsForPoint';
18import usePermissionsForPoint from 'lib/usePermissionsForPoint';
19import { PROXY_TYPE, proxyTypeToHuman } from 'lib/proxy';
19import { PROXY_TYPE, proxyTypeToHuman } from 'lib/proxy';
20import { useLocalRouter } from 'lib/LocalRouter';
20import { useLocalRouter } from 'lib/LocalRouter';
21−import { formatDots } from 'lib/dateFormat';
22import capitalize from 'lib/capitalize';
21import capitalize from 'lib/capitalize';
23import { eqAddr } from 'lib/wallet';
22import { eqAddr } from 'lib/wallet';
24−
23+import MiniBackButton from 'components/MiniBackButton';
25−//TODO consolidate with azimuth-js' getActivationBlock
24+import FooterButton from 'components/FooterButton';
26−const getRekeyDate = async (web3, contracts, point) => {
27− const logs = await contracts.azimuth.getPastEvents('ChangedKeys', {
28− fromBlock: 0,
29− toBlock: 'latest',
30− filter: { point: [point] },
31− });
32− if (logs.length === 0) {
33− return null;
34− } else {
35− // last log is most recent
36− const block = await web3.eth.getBlock(logs[logs.length - 1].blockNumber);
37− return new Date(block.timestamp * 1000);
38− }
39−};
40
25
41export default function AdminEditPermissions() {
26export default function AdminEditPermissions() {
42− const { push, names } = useLocalRouter();
27+ const { pop, push, names } = useLocalRouter();
43− const { web3, contracts } = useNetwork();
28+ const { contracts } = useNetwork();
44 const { wallet } = useWallet();
29 const { wallet } = useWallet();
45 const { pointCursor } = usePointCursor();
30 const { pointCursor } = usePointCursor();
46− const { getDetails } = usePointCache();
31+ const { getDetails, getRekeyDate } = usePointCache();
47
32
48− const _web3 = need.web3(web3);
49 const _contracts = need.contracts(contracts);
33 const _contracts = need.contracts(contracts);
50
34
51− const [rekeyDate, setRekeyDate] = useState(Nothing());
52−
53 const point = need.point(pointCursor);
35 const point = need.point(pointCursor);
54 const pointSize = azimuth.getPointSize(point);
36 const pointSize = azimuth.getPointSize(point);
55 const isParent = pointSize !== azimuth.PointSize.Planet;
37 const isParent = pointSize !== azimuth.PointSize.Planet;
@@ -57,42 +39,33 @@ export default function AdminEditPermissions() {
57 const userAddress = need.wallet(wallet).address;
39 const userAddress = need.wallet(wallet).address;
58
40
59 const details = need.details(getDetails(point));
41 const details = need.details(getDetails(point));
42+ const networkRevision = parseInt(details.keyRevisionNumber, 10);
60 const { canManage, isOwner } = usePermissionsForPoint(userAddress, point);
43 const { canManage, isOwner } = usePermissionsForPoint(userAddress, point);
61
44
62− useLifecycle(() => {
63− getRekeyDate(_web3, _contracts, point).then(res => {
64− if (res === null) setRekeyDate(Just(new Date()));
65− else setRekeyDate(Just(res));
66− });
67− });
68−
69− const goToProxy = useCallback(
45+ const goSetProxy = useCallback(
70 proxyType => push(names.SET_PROXY, { proxyType }),
46 proxyType => push(names.SET_PROXY, { proxyType }),
71 [push, names]
47 [push, names]
72 );
48 );
73
49
74− const goSetKeys = useCallback(() => push(names.SET_KEYS), [push, names]);
50+ const goNetworkingKeys = useCallback(() => push(names.NETWORKING_KEYS), [
51+ push,
52+ names,
53+ ]);
75
54
76− const proxyAction = (proxyType, address, enabled) => {
55+ const renderProxyAction = (proxyType, address) => {
77 if (eqAddr(address, ETH_ZERO_ADDR)) {
56 if (eqAddr(address, ETH_ZERO_ADDR)) {
78 address = 'Not set';
57 address = 'Not set';
79 } else if (eqAddr(address, _contracts.delegatedSending.address)) {
58 } else if (eqAddr(address, _contracts.delegatedSending.address)) {
80− address = address + ' (invites contract)';
59+ address = `${address} (invites contract)`;
81 }
60 }
82
61
83− const detail = enabled
84− ? address
85− : `You do not have permission to change the ${proxyTypeToHuman(
86− proxyType
87− )} proxy.`;
88−
89 return (
62 return (
90 <>
63 <>
91 <Grid.Item full>
64 <Grid.Item full>
92 <ForwardButton
65 <ForwardButton
93− disabled={!enabled}
66+ disabled={!isOwner}
94− onClick={() => goToProxy(proxyType)}
67+ onClick={() => goSetProxy(proxyType)}
95− detail={detail}>
68+ detail={address}>
96 {capitalize(proxyTypeToHuman(proxyType))} Proxy Address
69 {capitalize(proxyTypeToHuman(proxyType))} Proxy Address
97 </ForwardButton>
70 </ForwardButton>
98 </Grid.Item>
71 </Grid.Item>
@@ -101,18 +74,30 @@ export default function AdminEditPermissions() {
101 );
74 );
102 };
75 };
103
76
104− const networkKeysDetail =
77+ const renderNetworkKeysDetail = () => {
105− details.keyRevisionNumber === 0 ? (
78+ if (networkRevision === 0) {
106− 'Not yet configured'
79+ return 'Not yet configured.';
107− ) : (
80+ }
81+
82+ const renderRevision = maybeDate => (
108 <>
83 <>
109− Revision #{details.keyRevisionNumber}, since{' '}
84+ Revision #{networkRevision}, since {matchBlinkyDate(maybeDate)}
110− {matchBlinkyDate(rekeyDate)}
111 </>
85 </>
112 );
86 );
113
87
88+ return getRekeyDate(point).matchWith({
89+ Nothing: () => renderRevision(Nothing()),
90+ Just: p =>
91+ p.value.matchWith({
92+ Ok: r => renderRevision(Just(r.value)),
93+ Error: 'Unknown',
94+ }),
95+ });
96+ };
97+
114 return (
98 return (
115 <Grid>
99 <Grid>
100+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
116 <Grid.Item full as={ViewHeader}>
101 <Grid.Item full as={ViewHeader}>
117 Permissions
102 Permissions
118 </Grid.Item>
103 </Grid.Item>
@@ -122,19 +107,19 @@ export default function AdminEditPermissions() {
122 </Grid.Item>
107 </Grid.Item>
123 <Grid.Divider />
108 <Grid.Divider />
124
109
125− {proxyAction(PROXY_TYPE.MANAGEMENT, details.managementProxy, isOwner)}
110+ {renderProxyAction(PROXY_TYPE.MANAGEMENT, details.managementProxy)}
126
111
127− {isParent && proxyAction(PROXY_TYPE.SPAWN, details.spawnProxy, isOwner)}
112+ {isParent && renderProxyAction(PROXY_TYPE.SPAWN, details.spawnProxy)}
128
113
129− {isSenate && proxyAction(PROXY_TYPE.VOTING, details.votingProxy, isOwner)}
114+ {isSenate && renderProxyAction(PROXY_TYPE.VOTING, details.votingProxy)}
130
115
131 <Grid.Item
116 <Grid.Item
132 full
117 full
133 as={ForwardButton}
118 as={ForwardButton}
134 disabled={!canManage}
119 disabled={!canManage}
135− onClick={goSetKeys}
120+ onClick={goNetworkingKeys}
136− detail={networkKeysDetail}>
121+ detail={renderNetworkKeysDetail()}>
137− Network Keys
122+ Networking Keys
138 </Grid.Item>
123 </Grid.Item>
139 <Grid.Divider />
124 <Grid.Divider />
140 </Grid>
125 </Grid>
src/views/Admin/AdminHome.js+18−28
@@ -3,23 +3,21 @@ import { Just } from 'folktale/maybe';
3import { Grid } from 'indigo-react';
3import { Grid } from 'indigo-react';
4import * as need from 'lib/need';
4import * as need from 'lib/need';
5
5
6−import { ForwardButton, DownloadButton } from 'components/Buttons';
6+import { ForwardButton } from 'components/Buttons';
7
7
8−import { useHistory } from 'store/history';
9import { useWallet } from 'store/wallet';
8import { useWallet } from 'store/wallet';
10import { usePointCursor } from 'store/pointCursor';
9import { usePointCursor } from 'store/pointCursor';
11
10
12−import useKeyfileGenerator from 'lib/useKeyfileGenerator';
13import usePermissionsForPoint from 'lib/usePermissionsForPoint';
11import usePermissionsForPoint from 'lib/usePermissionsForPoint';
14
12
15import FooterButton from 'components/FooterButton';
13import FooterButton from 'components/FooterButton';
16import ViewHeader from 'components/ViewHeader';
14import ViewHeader from 'components/ViewHeader';
17−import Blinky from 'components/Blinky';
18import { useLocalRouter } from 'lib/LocalRouter';
15import { useLocalRouter } from 'lib/LocalRouter';
16+import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
17+import MiniBackButton from 'components/MiniBackButton';
19
18
20export default function AdminHome() {
19export default function AdminHome() {
21− const history = useHistory();
22− const { push, names } = useLocalRouter();
20+ const { push, names, pop } = useLocalRouter();
23 const { urbitWallet, wallet } = useWallet();
21 const { urbitWallet, wallet } = useWallet();
24 const { pointCursor } = usePointCursor();
22 const { pointCursor } = usePointCursor();
25
23
@@ -27,27 +25,20 @@ export default function AdminHome() {
27 const address = need.addressFromWallet(wallet);
25 const address = need.addressFromWallet(wallet);
28 const { isOwner, canTransfer } = usePermissionsForPoint(address, point);
26 const { isOwner, canTransfer } = usePermissionsForPoint(address, point);
29
27
30− const {
31− generating: generatingKeyfile,
32− available: keyfileAvailable,
33− generateAndDownload: generateAndDownloadKeyfile,
34− } = useKeyfileGenerator(point);
35−
36 const goRedownload = useCallback(() => push(names.REDOWNLOAD), [push, names]);
28 const goRedownload = useCallback(() => push(names.REDOWNLOAD), [push, names]);
37 const goReticket = useCallback(() => push(names.RETICKET), [push, names]);
29 const goReticket = useCallback(() => push(names.RETICKET), [push, names]);
38 const goEditPermissions = useCallback(() => push(names.EDIT_PERMISSIONS), [
30 const goEditPermissions = useCallback(() => push(names.EDIT_PERMISSIONS), [
39 push,
31 push,
40 names,
32 names,
41 ]);
33 ]);
42− const goTransfer = useCallback(() => history.push(history.names.TRANSFER), [
34+ const goTransfer = useCallback(() => push(names.TRANSFER), [names, push]);
43− history,
44− ]);
45
35
46 const canDownloadPassport = Just.hasInstance(urbitWallet);
36 const canDownloadPassport = Just.hasInstance(urbitWallet);
47
37
48 return (
38 return (
49 <>
39 <>
50 <Grid>
40 <Grid>
41+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
51 <Grid.Item full as={ViewHeader}>
42 <Grid.Item full as={ViewHeader}>
52 Admin
43 Admin
53 </Grid.Item>
44 </Grid.Item>
@@ -63,13 +54,9 @@ export default function AdminHome() {
63 <Grid.Divider />
54 <Grid.Divider />
64 <Grid.Item
55 <Grid.Item
65 full
56 full
66− as={DownloadButton}
57+ as={DownloadKeyfileButton}
67− disabled={!keyfileAvailable}
68− accessory={generatingKeyfile ? <Blinky /> : undefined}
69− onClick={generateAndDownloadKeyfile}
70− detail="Download your Arvo Keyfile">
58+ detail="Download your Arvo Keyfile"
71− Download Arvo Keyfile
59+ />
72− </Grid.Item>
73 <Grid.Divider />
60 <Grid.Divider />
74 <Grid.Item
61 <Grid.Item
75 full
62 full
@@ -88,13 +75,16 @@ export default function AdminHome() {
88 Edit Permissions
75 Edit Permissions
89 </Grid.Item>
76 </Grid.Item>
90 <Grid.Divider />
77 <Grid.Divider />
78+ <Grid.Item
79+ full
80+ as={ForwardButton}
81+ onClick={goTransfer}
82+ detail="Transfer ownership of this point"
83+ disabled={!canTransfer}>
84+ Transfer
85+ </Grid.Item>
86+ <Grid.Divider />
91 </Grid>
87 </Grid>
92− <FooterButton
93− detail="Transfer this identity to a new owner"
94− disabled={!canTransfer}
95− onClick={goTransfer}>
96− Transfer
97− </FooterButton>
98 </>
88 </>
99 );
89 );
100}
90}
src/views/Admin/AdminNetworkingKeys.jsnew file+220
@@ -0,0 +1,220 @@
1+import React, { useCallback, useState } from 'react';
2+import { Nothing } from 'folktale/maybe';
3+import { Grid, Text, H5, Flex } from 'indigo-react';
4+
5+import { usePointCursor } from 'store/pointCursor';
6+import { usePointCache } from 'store/pointCache';
7+
8+import { useLocalRouter } from 'lib/LocalRouter';
9+import * as need from 'lib/need';
10+import { segmentNetworkKey } from 'lib/keys';
11+import { formatDotsWithTime } from 'lib/dateFormat';
12+
13+import ViewHeader from 'components/ViewHeader';
14+import MiniBackButton from 'components/MiniBackButton';
15+import {
16+ GenerateButton,
17+ BootArvoButton,
18+ ForwardButton,
19+} from 'components/Buttons';
20+import FooterButton from 'components/FooterButton';
21+import WarningBox from 'components/WarningBox';
22+import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
23+
24+const renderNetworkKey = key => {
25+ const segments = segmentNetworkKey(key);
26+
27+ if (!segments) {
28+ return 'Unset';
29+ }
30+
31+ return segments.join('\n');
32+};
33+
34+function useSetKeys() {
35+ const setting = false;
36+ const didSet = false;
37+ const setKeys = useCallback(async () => {}, []);
38+
39+ return {
40+ setting,
41+ didSet,
42+ setKeys,
43+ };
44+}
45+
46+export default function AdminNetworkingKeys() {
47+ const { push, names, pop } = useLocalRouter();
48+ const { pointCursor } = usePointCursor();
49+ const { getDetails, getRekeyDate } = usePointCache();
50+
51+ const point = need.point(pointCursor);
52+ const details = getDetails(point);
53+
54+ const hasKeys = details.matchWith({
55+ Nothing: () => false,
56+ Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
57+ });
58+ const [configuring, setConfiguring] = useState(false);
59+
60+ const { setKeys, setting, didSet } = useSetKeys();
61+
62+ const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
63+ const configureTransaction = useCallback(() => setConfiguring(true), [
64+ setConfiguring,
65+ ]);
66+
67+ const inDefaultState = !configuring && !setting && !didSet;
68+
69+ const renderTitle = () => {
70+ if (didSet) {
71+ return 'Networking keys are now set. Download your Keyfile to authenticate Arvo.';
72+ }
73+
74+ if (setting) {
75+ return 'Setting Network Keys...';
76+ }
77+
78+ if (!hasKeys) {
79+ return 'Networking keys have not yet been set.';
80+ }
81+
82+ return 'Networking';
83+ };
84+
85+ const renderButton = () => {
86+ if (didSet) {
87+ return <Grid.Item full as={DownloadKeyfileButton} solid />;
88+ }
89+
90+ if (configuring || setting) {
91+ return <Grid.Item full as={GenerateButton} solid onClick={setKeys} />;
92+ }
93+
94+ return (
95+ <Grid.Item full as={ForwardButton} solid onClick={configureTransaction}>
96+ {hasKeys ? 'Reset' : 'Set'} Networking Keys
97+ </Grid.Item>
98+ );
99+ };
100+
101+ const renderNetworkKeySection = (title, key) => (
102+ <>
103+ <Grid.Item full as={H5} className="mt3 gray4">
104+ {title}
105+ </Grid.Item>
106+ {key.matchWith({
107+ Nothing: () => (
108+ <Grid.Item full as={Text} className="f5 gray4">
109+ Unset
110+ </Grid.Item>
111+ ),
112+ Just: ({ value: key }) => (
113+ <>
114+ <Grid.Item full as="code" className="f5 wrap">
115+ 0x
116+ </Grid.Item>
117+ <Grid.Item full as="code" className="f5 wrap">
118+ {renderNetworkKey(key)}
119+ </Grid.Item>
120+ </>
121+ ),
122+ })}
123+ </>
124+ );
125+
126+ const renderDetail = (title, value) => (
127+ <Flex.Item as={Flex} col>
128+ <Flex.Item as={H5} className="gray4">
129+ {title}
130+ </Flex.Item>
131+ {value.matchWith({
132+ Nothing: () => (
133+ <Flex.Item as={Text} className="f5 gray4">
134+ Unset
135+ </Flex.Item>
136+ ),
137+ Just: ({ value }) => <Flex.Item as={Text}>{value}</Flex.Item>,
138+ })}
139+ </Flex.Item>
140+ );
141+
142+ const renderDetails = () => {
143+ return (
144+ <>
145+ {renderNetworkKeySection(
146+ 'Authentication',
147+ details.map(d => d.authenticationKey)
148+ )}
149+ {renderNetworkKeySection(
150+ 'Encryption',
151+ details.map(d => d.encryptionKey)
152+ )}
153+ <Grid.Item full as={Flex} row justify="between" className="mt3">
154+ {renderDetail('Revision', details.map(d => d.keyRevisionNumber))}
155+ {renderDetail('Continuity Era', details.map(d => d.continuityNumber))}
156+ {renderDetail(
157+ 'Crypto Suite Ver.',
158+ details.map(d => d.cryptoSuiteVersion)
159+ )}
160+ </Grid.Item>
161+ {hasKeys && (
162+ <Grid.Item full as={Flex} row justify="between" className="mt3">
163+ {renderDetail(
164+ 'Last Set',
165+ getRekeyDate(point).map(date =>
166+ date.matchWith({
167+ Ok: r => formatDotsWithTime(r.value),
168+ Error: () => Nothing(),
169+ })
170+ )
171+ )}
172+ </Grid.Item>
173+ )}
174+ </>
175+ );
176+ };
177+
178+ return (
179+ <>
180+ <Grid>
181+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
182+
183+ <Grid.Item full as={ViewHeader}>
184+ {renderTitle()}
185+ </Grid.Item>
186+
187+ {inDefaultState && (
188+ <Grid.Item full as={Text} className="mb3">
189+ {hasKeys
190+ ? 'Here are your public keys that authenticate your Arvo.'
191+ : 'Set your networking keys to authenticate with Arvo.'}
192+ </Grid.Item>
193+ )}
194+
195+ {didSet && (
196+ <Grid.Item full as={WarningBox} className="mb3">
197+ You need this keyfile to authenticate with Arvo.
198+ </Grid.Item>
199+ )}
200+
201+ {renderButton()}
202+
203+ {inDefaultState && renderDetails()}
204+
205+ {didSet && (
206+ <>
207+ <Grid.Item full as={BootArvoButton} disabled />
208+ <Grid.Divider />
209+ </>
210+ )}
211+ </Grid>
212+
213+ {inDefaultState && (
214+ <FooterButton onClick={goRelocate} disabled>
215+ Relocate
216+ </FooterButton>
217+ )}
218+ </>
219+ );
220+}
src/views/Admin/AdminRedownload.js+5−3
@@ -10,9 +10,10 @@ import PaperRenderer from 'components/PaperRenderer';
10import { downloadWallet } from 'lib/invite';
10import { downloadWallet } from 'lib/invite';
11
11
12import { useWallet } from 'store/wallet';
12import { useWallet } from 'store/wallet';
13−import { Grid, Text } from 'indigo-react';
13+import { Grid, P } from 'indigo-react';
14import ViewHeader from 'components/ViewHeader';
14import ViewHeader from 'components/ViewHeader';
15import { useLocalRouter } from 'lib/LocalRouter';
15import { useLocalRouter } from 'lib/LocalRouter';
16+import MiniBackButton from 'components/MiniBackButton';
16
17
17export default function AdminRedownload() {
18export default function AdminRedownload() {
18 const { urbitWallet } = useWallet();
19 const { urbitWallet } = useWallet();
@@ -34,11 +35,12 @@ export default function AdminRedownload() {
34
35
35 return (
36 return (
36 <>
37 <>
37− <Grid gap={4}>
38+ <Grid>
39+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
38 <Grid.Item full as={ViewHeader}>
40 <Grid.Item full as={ViewHeader}>
39 Download Passport
41 Download Passport
40 </Grid.Item>
42 </Grid.Item>
41− <Grid.Item full as={Text}>
43+ <Grid.Item full as={P}>
42 After you’ve downloaded your passport, back up the ticket manually or
44 After you’ve downloaded your passport, back up the ticket manually or
43 store on a trusted device.
45 store on a trusted device.
44 </Grid.Item>
46 </Grid.Item>
src/views/Admin/AdminSetProxy.js+34−12
@@ -1,12 +1,12 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2−import { Grid, P, Text, Input } from 'indigo-react';
2+import { Grid, P, Text, Input, Flex, LinkButton } from 'indigo-react';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4
4
5import { useNetwork } from 'store/network';
5import { useNetwork } from 'store/network';
6
6
7import * as need from 'lib/need';
7import * as need from 'lib/need';
8import { useLocalRouter } from 'lib/LocalRouter';
8import { useLocalRouter } from 'lib/LocalRouter';
9−import { ETH_ZERO_ADDR } from 'lib/wallet';
9+import { ETH_ZERO_ADDR, eqAddr } from 'lib/wallet';
10
10
11import ViewHeader from 'components/ViewHeader';
11import ViewHeader from 'components/ViewHeader';
12import { usePointCursor } from 'store/pointCursor';
12import { usePointCursor } from 'store/pointCursor';
@@ -64,12 +64,22 @@ function useSetProxy(proxyType) {
64 [proxyType, contracts, pointCursor]
64 [proxyType, contracts, pointCursor]
65 );
65 );
66
66
67− const generate = useCallback(async () => {
67+ const generate = useCallback(
68− //
68+ async address => {
69+ const utx = buildUnsignedTx(address);
70+ console.log(utx);
69− }, []);
71+ },
72+ [buildUnsignedTx]
73+ );
74+
75+ const unset = useCallback(() => generate(ETH_ZERO_ADDR), [generate]);
76+
77+ const locked = false;
70
78
71 return {
79 return {
80+ locked,
72 generate,
81 generate,
82+ unset,
73 };
83 };
74}
84}
75
85
@@ -82,12 +92,17 @@ export default function AdminSetProxy() {
82
92
83 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
93 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
84
94
95+ const { generate, unset, locked } = useSetProxy(data.proxyType);
85− const [addressInput, { pass }] = useAddressInput({
96+ const [addressInput, { pass, data: address }] = useAddressInput({
86 name: 'address',
97 name: 'address',
87 label: `New ${properProxyType} Address`,
98 label: `New ${properProxyType} Address`,
99+ disabled: locked,
88 });
100 });
89
101
90− const { generate } = useSetProxy(data.proxyType);
102+ const _generate = useCallback(() => generate(address), [address, generate]);
103+
104+ const proxyAddress = proxyFromDetails(details, data.proxyType);
105+ const isProxySet = !eqAddr(ETH_ZERO_ADDR, proxyAddress);
91
106
92 return (
107 return (
93 <Grid>
108 <Grid>
@@ -95,19 +110,26 @@ export default function AdminSetProxy() {
95 <Grid.Item full as={ViewHeader}>
110 <Grid.Item full as={ViewHeader}>
96 {properProxyType} Address
111 {properProxyType} Address
97 </Grid.Item>
112 </Grid.Item>
98− <Grid.Item full as={P}>
113+ <Grid.Item full as={Text} className="mb4">
99 {proxyTypeToHumanDescription(data.proxyType)}
114 {proxyTypeToHumanDescription(data.proxyType)}
100 </Grid.Item>
115 </Grid.Item>
101− <Grid.Item full as={Text} className="f6">
116+ <Grid.Item full as={Text} className="f6 mb1">
102 Current {properProxyType} Address
117 Current {properProxyType} Address
103 </Grid.Item>
118 </Grid.Item>
119+ <Grid.Item full as={Flex} row justify="between" align="center">
104− <Grid.Item full as={Text} className="mono">
120+ <Flex.Item flex as={Text} className="mono">
105− {proxyFromDetails(details, data.proxyType)}
121+ {isProxySet ? proxyAddress : 'Unset'}
122+ </Flex.Item>
123+ {isProxySet && (
124+ <Flex.Item as={LinkButton} onClick={unset}>
125+ Unset
126+ </Flex.Item>
127+ )}
106 </Grid.Item>
128 </Grid.Item>
107
129
108 <Grid.Item full as={Input} {...addressInput} className="mv4" />
130 <Grid.Item full as={Input} {...addressInput} className="mv4" />
109
131
110− <Grid.Item full as={GenerateButton} onClick={generate}>
132+ <Grid.Item full as={GenerateButton} onClick={_generate} disabled={!pass}>
111 Generate & Sign Transaction
133 Generate & Sign Transaction
112 </Grid.Item>
134 </Grid.Item>
113 </Grid>
135 </Grid>
src/views/Admin/AdminTransfer.jsnew file+47
@@ -0,0 +1,47 @@
1+import React, { useCallback } from 'react';
2+import { Grid, P, Text, Input } from 'indigo-react';
3+import { useLocalRouter } from 'lib/LocalRouter';
4+
5+import ViewHeader from 'components/ViewHeader';
6+import MiniBackButton from 'components/MiniBackButton';
7+import { GenerateButton } from 'components/Buttons';
8+import { useAddressInput } from 'lib/useInputs';
9+import useCurrentPointName from 'lib/useCurrentPointName';
10+
11+function useTransfer() {
12+ const transfer = useCallback(async () => {}, []);
13+
14+ return {
15+ transfer,
16+ };
17+}
18+
19+export default function AdminTransfer() {
20+ const { pop } = useLocalRouter();
21+ const name = useCurrentPointName();
22+
23+ const [addressInput, { pass }] = useAddressInput({
24+ name: 'address',
25+ label: `Ethereum Address`,
26+ });
27+
28+ const { transfer } = useTransfer();
29+
30+ return (
31+ <Grid>
32+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
33+ <Grid.Item full as={ViewHeader}>
34+ Transfer Point
35+ </Grid.Item>
36+ <Grid.Item full as={P}>
37+ Transfer {name} to a new owner.
38+ </Grid.Item>
39+
40+ <Grid.Item full as={Input} {...addressInput} className="mv4" />
41+
42+ <Grid.Item full as={GenerateButton} onClick={transfer} disabled={!pass}>
43+ Generate & Sign Transaction
44+ </Grid.Item>
45+ </Grid>
46+ );
47+}
src/views/GenKeyfile.js+1−1
@@ -33,7 +33,7 @@ class GenKeyfile extends React.Component {
33 // in case we did SetKeys earlier this session, make sure to generate the
33 // in case we did SetKeys earlier this session, make sure to generate the
34 // newer keyfile, rather than the one that will expire soon
34 // newer keyfile, rather than the one that will expire soon
35 const revision = this.props.networkRevision.matchWith({
35 const revision = this.props.networkRevision.matchWith({
36− Nothing: () => parseInt(pointDetails.keyRevisionNumber),
36+ Nothing: () => parseInt(pointDetails.keyRevisionNumber, 10),
37 Just: p => p.value,
37 Just: p => p.value,
38 });
38 });
39
39
src/views/Invite.js+4−4
@@ -53,8 +53,8 @@ export default function Invite() {
53 const acceptedInvitesText = matchBlinky(acceptedInvites);
53 const acceptedInvitesText = matchBlinky(acceptedInvites);
54
54
55 return (
55 return (
56+ <View inset>
56− <LocalRouterProvider value={router}>
57+ <LocalRouterProvider value={router}>
57− <View inset>
58 <Grid className="mb4">
58 <Grid className="mb4">
59 <Grid.Item
59 <Grid.Item
60 as={Crumbs}
60 as={Crumbs}
@@ -82,7 +82,7 @@ export default function Invite() {
82 </Grid.Item>
82 </Grid.Item>
83 </Grid>
83 </Grid>
84 <Route />
84 <Route />
85− </View>
85+ </LocalRouterProvider>
86− </LocalRouterProvider>
86+ </View>
87 );
87 );
88}
88}
src/views/Point.js+3−9
@@ -1,4 +1,4 @@
1−import React, { useCallback, useMemo } from 'react';
1+import React, { useCallback } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3import * as need from 'lib/need';
3import * as need from 'lib/need';
4import { Grid } from 'indigo-react';
4import { Grid } from 'indigo-react';
@@ -9,21 +9,17 @@ import { useHistory } from 'store/history';
9
9
10import View from 'components/View';
10import View from 'components/View';
11import Passport from 'components/Passport';
11import Passport from 'components/Passport';
12−import { ForwardButton } from 'components/Buttons';
12+import { ForwardButton, BootArvoButton } from 'components/Buttons';
13import FooterButton from 'components/FooterButton';
13import FooterButton from 'components/FooterButton';
14import { matchBlinky } from 'components/Blinky';
14import { matchBlinky } from 'components/Blinky';
15
15
16import useInvites from 'lib/useInvites';
16import useInvites from 'lib/useInvites';
17import { useSyncOwnedPoints } from 'lib/useSyncPoints';
17import { useSyncOwnedPoints } from 'lib/useSyncPoints';
18−import { ROUTE_NAMES } from 'lib/routeNames';
19
18
20import Actions from './Point/Actions';
19import Actions from './Point/Actions';
21import { useWallet } from 'store/wallet';
20import { useWallet } from 'store/wallet';
22import usePermissionsForPoint from 'lib/usePermissionsForPoint';
21import usePermissionsForPoint from 'lib/usePermissionsForPoint';
23
22
24−const isPlanet = point =>
25− azimuth.getPointSize(point) === azimuth.PointSize.Planet;
26−
27export default function Point() {
23export default function Point() {
28 const { push, names } = useHistory();
24 const { push, names } = useHistory();
29 const { pointCursor } = usePointCursor();
25 const { pointCursor } = usePointCursor();
@@ -91,9 +87,7 @@ export default function Point() {
91 Admin
87 Admin
92 </Grid.Item>
88 </Grid.Item>
93 <Grid.Divider />
89 <Grid.Divider />
94− <Grid.Item full as={ForwardButton} detail="Boot your computer" disabled>
90+ <Grid.Item full as={BootArvoButton} disabled />
95− Boot Arvo
96− </Grid.Item>
97 {showActions && (
91 {showActions && (
98 <Grid.Item full>
92 <Grid.Item full>
99 <Actions />
93 <Actions />