on master help sign in
feat: indigo-ify reticket component
d1230bbMatt Condon (shrugs)
src/components/Steps.js+2−4
@@ -2,11 +2,9 @@ import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Text } from 'indigo-react';
3import { Text } from 'indigo-react';
4
4
5−export default function Steps({ num = 1, total = 3, className }) {
5+export default function Steps({ num = 1, total = 3, className, ...rest }) {
6− // NOTE: the 48px must match the `mt8` class on Passport for things to line
7− // up nicely
8 return (
6 return (
9− <Text className={cn('f5 gray3', className)} style={{ height: '48px' }}>
7+ <Text className={cn('f5 gray3', className)} {...rest}>
10 <Text className="f5 black">Step {num}</Text> of {total}
8 <Text className="f5 black">Step {num}</Text> of {total}
11 </Text>
9 </Text>
12 );
10 );
src/indigo-react/components/Button.js+4−4
@@ -48,12 +48,12 @@ export default function Button({
48 }}
48 }}
49 onClick={!disabled && onClick ? onClick : undefined}
49 onClick={!disabled && onClick ? onClick : undefined}
50 {...rest}>
50 {...rest}>
51− <Grid.Item as={Flex} justify="between" full>
51+ <Grid.Item full as={Flex} justify="between" className={cn(textColor)}>
52− <span className={cn(textColor)}>{children}</span>
52+ <span>{children}</span>
53− <div className={cn('pl4', textColor)}>{accessory}</div>
53+ <div className={cn('pl4')}>{accessory}</div>
54 </Grid.Item>
54 </Grid.Item>
55 {detail && (
55 {detail && (
56− <Grid.Item as={HelpText} full>
56+ <Grid.Item full as={HelpText}>
57 {detail}
57 {detail}
58 </Grid.Item>
58 </Grid.Item>
59 )}
59 )}
src/indigo-react/components/IconButton.js−1
@@ -37,7 +37,6 @@ export default function IconButton({
37 height: '1rem',
37 height: '1rem',
38 fontSize: '1rem',
38 fontSize: '1rem',
39 fontWeight: 'bold',
39 fontWeight: 'bold',
40− // TODO: ^ replace with 1:1 svgs and remove explicit sizing
41 ...(disabled && {
40 ...(disabled && {
42 pointerEvents: 'none',
41 pointerEvents: 'none',
43 cursor: 'not-allowed',
42 cursor: 'not-allowed',
src/lib/constants.js+5
@@ -1,3 +1,7 @@
1+import { isDevelopment } from './flags';
2+
3+const CHECK_BLOCK_EVERY_MS = isDevelopment ? 1000 : 5000;
4+
1const MIN_GALAXY = 0;
5const MIN_GALAXY = 0;
2const MAX_GALAXY = 255;
6const MAX_GALAXY = 255;
3const MIN_STAR = 256;
7const MIN_STAR = 256;
@@ -63,6 +67,7 @@ const PROFILE_STATES = {
63};
67};
64
68
65export {
69export {
70+ CHECK_BLOCK_EVERY_MS,
66 GAS_LIMITS,
71 GAS_LIMITS,
67 GEN_STATES,
72 GEN_STATES,
68 BUTTON_STATES,
73 BUTTON_STATES,
src/lib/invite.js+15−15
@@ -30,8 +30,8 @@ const TRANSACTION_STATES = {
30 label: 'Funding Transactions...',
30 label: 'Funding Transactions...',
31 progress: 0.3,
31 progress: 0.3,
32 },
32 },
33− CLAIMING: {
33+ TRANSFERRING: {
34− label: 'Claiming Invite...',
34+ label: 'Transferring Point...',
35 progress: 0.55,
35 progress: 0.55,
36 },
36 },
37 CLEANING: {
37 CLEANING: {
@@ -81,9 +81,9 @@ export async function downloadWallet(paper) {
81 });
81 });
82}
82}
83
83
84−export async function claimPointFromInvite({
84+export async function reticketPointBetweenWallets({
85− inviteWallet,
85+ fromWallet,
86− wallet,
86+ toWallet,
87 point,
87 point,
88 web3,
88 web3,
89 contracts,
89 contracts,
@@ -99,7 +99,7 @@ export async function claimPointFromInvite({
99 const gotFunding = () => onUpdate({ type: 'gotFunding' });
99 const gotFunding = () => onUpdate({ type: 'gotFunding' });
100 const progress = state => onUpdate({ type: 'progress', state });
100 const progress = state => onUpdate({ type: 'progress', state });
101
101
102− const inviteAddress = inviteWallet.address;
102+ const inviteAddress = fromWallet.address;
103
103
104 //
104 //
105 // generate transactions
105 // generate transactions
@@ -124,7 +124,7 @@ export async function claimPointFromInvite({
124
124
125 // configure networking public keys
125 // configure networking public keys
126 const seed = await deriveNetworkSeedFromUrbitWallet(
126 const seed = await deriveNetworkSeedFromUrbitWallet(
127− Just(wallet),
127+ Just(toWallet),
128 INITIAL_NETWORK_KEY_REVISION
128 INITIAL_NETWORK_KEY_REVISION
129 );
129 );
130
130
@@ -152,7 +152,7 @@ export async function claimPointFromInvite({
152 const managementTx = azimuth.ecliptic.setManagementProxy(
152 const managementTx = azimuth.ecliptic.setManagementProxy(
153 contracts,
153 contracts,
154 point,
154 point,
155− wallet.management.keys.address
155+ toWallet.management.keys.address
156 );
156 );
157 managementTx.gas = GAS_LIMITS.SET_PROXY;
157 managementTx.gas = GAS_LIMITS.SET_PROXY;
158
158
@@ -165,7 +165,7 @@ export async function claimPointFromInvite({
165 const spawnTx = azimuth.ecliptic.setSpawnProxy(
165 const spawnTx = azimuth.ecliptic.setSpawnProxy(
166 contracts,
166 contracts,
167 point,
167 point,
168− wallet.spawn.keys.address
168+ toWallet.spawn.keys.address
169 );
169 );
170 spawnTx.gas = GAS_LIMITS.SET_PROXY;
170 spawnTx.gas = GAS_LIMITS.SET_PROXY;
171 txs.push(spawnTx);
171 txs.push(spawnTx);
@@ -176,7 +176,7 @@ export async function claimPointFromInvite({
176 const votingTx = azimuth.ecliptic.setVotingProxy(
176 const votingTx = azimuth.ecliptic.setVotingProxy(
177 contracts,
177 contracts,
178 point,
178 point,
179− wallet.voting.keys.address
179+ toWallet.voting.keys.address
180 );
180 );
181 votingTx.gas = GAS_LIMITS.SET_PROXY;
181 votingTx.gas = GAS_LIMITS.SET_PROXY;
182 txs.push(votingTx);
182 txs.push(votingTx);
@@ -188,7 +188,7 @@ export async function claimPointFromInvite({
188 const transferFinalTx = azimuth.ecliptic.transferPoint(
188 const transferFinalTx = azimuth.ecliptic.transferPoint(
189 contracts,
189 contracts,
190 point,
190 point,
191− wallet.ownership.keys.address,
191+ toWallet.ownership.keys.address,
192 false
192 false
193 );
193 );
194 transferFinalTx.gas = GAS_LIMITS.TRANSFER;
194 transferFinalTx.gas = GAS_LIMITS.TRANSFER;
@@ -212,7 +212,7 @@ export async function claimPointFromInvite({
212
212
213 let txPairs = txs.map(tx => {
213 let txPairs = txs.map(tx => {
214 let stx = new Tx(tx);
214 let stx = new Tx(tx);
215− stx.sign(Buffer.from(inviteWallet.privateKey, 'hex'));
215+ stx.sign(Buffer.from(fromWallet.privateKey, 'hex'));
216 return {
216 return {
217 raw: hexify(stx.serialize()),
217 raw: hexify(stx.serialize()),
218 signed: stx,
218 signed: stx,
@@ -239,7 +239,7 @@ export async function claimPointFromInvite({
239 // sending and awaiting transactions
239 // sending and awaiting transactions
240 //
240 //
241
241
242− progress(TRANSACTION_STATES.CLAIMING);
242+ progress(TRANSACTION_STATES.TRANSFERRING);
243
243
244 await sendAndAwaitConfirm(web3, txPairs.map(p => Just(p.signed)), usedTank);
244 await sendAndAwaitConfirm(web3, txPairs.map(p => Just(p.signed)), usedTank);
245
245
@@ -257,14 +257,14 @@ export async function claimPointFromInvite({
257 if (transferEth && balance > sendEthCost) {
257 if (transferEth && balance > sendEthCost) {
258 const value = balance - sendEthCost;
258 const value = balance - sendEthCost;
259 const tx = {
259 const tx = {
260− to: wallet.ownership.keys.address,
260+ to: toWallet.ownership.keys.address,
261 value: value,
261 value: value,
262 gasPrice: gasPrice,
262 gasPrice: gasPrice,
263 gas: gasLimit,
263 gas: gasLimit,
264 nonce: inviteNonce++,
264 nonce: inviteNonce++,
265 };
265 };
266 let stx = new Tx(tx);
266 let stx = new Tx(tx);
267− stx.sign(inviteWallet.privateKey);
267+ stx.sign(fromWallet.privateKey);
268 const rawTx = hexify(stx.serialize());
268 const rawTx = hexify(stx.serialize());
269 web3.eth.sendSignedTransaction(rawTx).catch(err => {
269 web3.eth.sendSignedTransaction(rawTx).catch(err => {
270 console.log('error sending value tx, who cares', err);
270 console.log('error sending value tx, who cares', err);
src/lib/tank.js+12−12
@@ -1,6 +1,7 @@
1// /lib/tank: functions for funding transactions
1// /lib/tank: functions for funding transactions
2
2
3import { waitForTransactionConfirm } from './txn';
3import { waitForTransactionConfirm } from './txn';
4+import { CHECK_BLOCK_EVERY_MS } from './constants';
4
5
5//NOTE if accessing this in a localhost configuration fails with "CORS request
6//NOTE if accessing this in a localhost configuration fails with "CORS request
6// did not succeed", you might need to visit localhost:3001 or whatever
7// did not succeed", you might need to visit localhost:3001 or whatever
@@ -104,27 +105,26 @@ async function waitForBalance(
104) {
105) {
105 console.log('tank: awaiting balance', address, minBalance);
106 console.log('tank: awaiting balance', address, minBalance);
106 return new Promise((resolve, reject) => {
107 return new Promise((resolve, reject) => {
107− let oldBalance = null;
108 const checkForBalance = async () => {
108 const checkForBalance = async () => {
109 const balance = await web3.eth.getBalance(address);
109 const balance = await web3.eth.getBalance(address);
110+
110 if (balance >= minBalance) {
111 if (balance >= minBalance) {
111− // if we ever asked for funding, retract that request now
112− if (gotFunding && oldBalance !== null) {
112+ if (gotFunding) {
113 gotFunding();
113 gotFunding();
114 }
114 }
115+
115 resolve();
116 resolve();
116 } else {
117 } else {
117− if (balance !== oldBalance) {
118− // TODO: minBalance is a `number` type
118+ // TODO: minBalance is a `number` type
119− // but we want to display ETH, which will never accept a number
119+ // but we want to display ETH, which will never accept a number
120− // but instead wants string or BN/BigNumber.
120+ // but instead wants string or BN/BigNumber.
121− // this will be heavily refactored to correctly do BN math, but until
121+ // this will be heavily refactored to correctly do BN math, but until
122− // then we'll manually convert this number to an integer string
122+ // then we'll manually convert this number to an integer string
123− askForFunding(address, minBalance.toFixed(), balance);
123+ askForFunding(address, minBalance.toFixed(), balance);
124− }
125− setTimeout(checkForBalance, 13000);
124+ setTimeout(checkForBalance, CHECK_BLOCK_EVERY_MS);
126 }
125 }
127 };
126 };
127+
128 checkForBalance();
128 checkForBalance();
129 });
129 });
130}
130}
src/lib/txn.js+1−3
@@ -8,9 +8,7 @@ import { NETWORK_TYPES } from './network';
8import { ledgerSignTransaction } from './ledger';
8import { ledgerSignTransaction } from './ledger';
9import { trezorSignTransaction } from './trezor';
9import { trezorSignTransaction } from './trezor';
10import { WALLET_TYPES, addHexPrefix } from './wallet';
10import { WALLET_TYPES, addHexPrefix } from './wallet';
11−
12−const CHECK_BLOCK_EVERY_MS =
11+import { CHECK_BLOCK_EVERY_MS } from './constants';
13− process.env.NODE_ENV === 'development' ? 1000 : 5000;
14
12
15const signTransaction = async config => {
13const signTransaction = async config => {
16 let {
14 let {
src/lib/useRouter.js+39−17
@@ -1,5 +1,5 @@
1−import { useCallback, useMemo, useState, useEffect } from 'react';
1+import { useCallback, useMemo, useState, useRef, useEffect } from 'react';
2−import { last, includes as _includes } from 'lodash';
2+import { last, includes as _includes, findIndex } from 'lodash';
3
3
4/**
4/**
5 * @param primary whether or not this is the top-level router
5 * @param primary whether or not this is the top-level router
@@ -15,6 +15,9 @@ export default function useRouter({
15}) {
15}) {
16 const [routes, setRoutes] = useState(initialRoutes);
16 const [routes, setRoutes] = useState(initialRoutes);
17
17
18+ const oldPopState = useRef();
19+
20+ const size = routes.length;
18 const push = useCallback(
21 const push = useCallback(
19 (key, data) => {
22 (key, data) => {
20 if (!views[key]) {
23 if (!views[key]) {
@@ -46,12 +49,25 @@ export default function useRouter({
46 [setRoutes, views]
49 [setRoutes, views]
47 );
50 );
48 const pop = useCallback(
51 const pop = useCallback(
49− (count = 1) =>
52+ (count = 1) => {
50− setRoutes(routes =>
53+ if (size > 1) {
54+ // pop as expected
51− routes.length > 1 ? [...routes.slice(0, routes.length - count)] : routes
55+ return setRoutes(routes => [...routes.slice(0, routes.length - count)]);
52− ),
56+ }
53− // ^ can't pop root route
57+
58+ // if we are at the root, pass this event to our parent
59+ if (oldPopState.current) {
60+ window.history.back();
61+ }
62+ },
54− [setRoutes]
63+ [size, setRoutes, oldPopState]
64+ );
65+ const popTo = useCallback(
66+ name => {
67+ const index = findIndex(routes, route => route.key === name);
68+ return pop(routes.length - 1 - index);
69+ },
70+ [pop, routes]
55 );
71 );
56 const peek = useCallback(() => last(routes), [routes]);
72 const peek = useCallback(() => last(routes), [routes]);
57 const replaceWith = useCallback(routes => setRoutes(() => routes), [
73 const replaceWith = useCallback(routes => setRoutes(() => routes), [
@@ -64,7 +80,6 @@ export default function useRouter({
64 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
80 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
65 routes,
81 routes,
66 ]);
82 ]);
67− const size = routes.length;
68 const data = useMemo(() => last(routes).data, [routes]);
83 const data = useMemo(() => last(routes).data, [routes]);
69 const Route = useMemo(() => views[peek().key], [views, peek]);
84 const Route = useMemo(() => views[peek().key], [views, peek]);
70
85
@@ -75,24 +90,30 @@ export default function useRouter({
75
90
76 // capture browser pop in primary router
91 // capture browser pop in primary router
77 useEffect(() => {
92 useEffect(() => {
93+ // store the previous onpopstate handler
78− const oldPopState = window.onpopstate;
94+ oldPopState.current = window.onpopstate;
79−
80− window.history.pushState(null, null, null);
81
95
96+ // construct new onpopstate handler
82 window.onpopstate = e => {
97 window.onpopstate = e => {
83− if (size <= 1) {
98+ if (size <= 1 && oldPopState.current) {
84− // if this is the root route, give the event to parent router
99+ // if this is the root route and there's a parent handler,
100+ // give the event to the handler
85− return oldPopState(e);
101+ return oldPopState.current(e);
86 }
102 }
87
103
104+ // on pop, tell the browser of a new state to avoid giving the user
105+ // the ability to go forward
88 window.history.pushState(null, null, null);
106 window.history.pushState(null, null, null);
107+
108+ // then update our local state for rendering
89 pop();
109 pop();
90 };
110 };
91
111
92 return () => {
112 return () => {
113+ // reset the onpopstate to the previous version
93− window.onpopstate = oldPopState;
114+ window.onpopstate = oldPopState.current;
94 };
115 };
95− }, [size, pop]);
116+ }, [size, pop, oldPopState]);
96
117
97 return {
118 return {
98 Route,
119 Route,
@@ -101,6 +122,7 @@ export default function useRouter({
101 push,
122 push,
102 popAndPush,
123 popAndPush,
103 pop,
124 pop,
125+ popTo,
104 replaceWith,
126 replaceWith,
105 reset,
127 reset,
106 peek,
128 peek,
src/views/Activate/PassportDownload.js+33−32
@@ -1,16 +1,16 @@
1import React, { useState, useEffect, useCallback } from 'react';
1import React, { useState, useEffect, useCallback } from 'react';
2−import cn from 'classnames';
3import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
4−import { Grid, H4, P } from 'indigo-react';
3+import { Grid, P } from 'indigo-react';
5
4
6import * as need from 'lib/need';
5import * as need from 'lib/need';
7−
8−import { useActivateFlow } from './ActivateFlow';
9import { downloadWallet } from 'lib/invite';
6import { downloadWallet } from 'lib/invite';
7+import { useLocalRouter } from 'lib/LocalRouter';
8+
10import { DownloadButton, ForwardButton } from 'components/Buttons';
9import { DownloadButton, ForwardButton } from 'components/Buttons';
11import PaperRenderer from 'components/PaperRenderer';
10import PaperRenderer from 'components/PaperRenderer';
12−import { useLocalRouter } from 'lib/LocalRouter';
11+
13−import Steps from 'components/Steps';
12+import { useActivateFlow } from './ActivateFlow';
13+import PassportView from './PassportView';
14
14
15export default function PassportDownload({ className }) {
15export default function PassportDownload({ className }) {
16 const {
16 const {
@@ -51,36 +51,37 @@ export default function PassportDownload({ className }) {
51 const goToVerify = useCallback(() => push(names.VERIFY), [push, names]);
51 const goToVerify = useCallback(() => push(names.VERIFY), [push, names]);
52
52
53 return (
53 return (
54+ <>
54− <Grid className={cn(className, 'auto-rows-min')}>
55+ <PassportView className={className} header="Passport" step={1}>
55− <Grid.Item full as={Steps} num={1} total={3} />
56− <Grid.Item full as={H4}>
57− Passport
58− </Grid.Item>
56+ <Grid>
59− <Grid.Item full>
57+ <Grid.Item full>
60− <P>
58+ <P>
61− After you’ve downloaded your passport, back up the ticket manually or
59+ After you’ve downloaded your passport, back up the ticket manually
62− store on a trusted device.
60+ or store on a trusted device.
63− </P>
61+ </P>
64− <P>
62+ <P>
65− What is digital identity? A passport is your digital identity. You
63+ What is digital identity? A passport is your digital identity. You
66− will use your passport to access your true computer, send payments,
64+ will use your passport to access your true computer, send
67− and administer your identity. So naturally, you must keep this secure.
65+ payments, and administer your identity. So naturally, you must
66+ keep this secure.
68− </P>
67+ </P>
69− </Grid.Item>
68+ </Grid.Item>
70− <Grid.Item
69+ <Grid.Item
71− full
70+ full
72− as={!downloaded ? DownloadButton : ForwardButton}
71+ as={!downloaded ? DownloadButton : ForwardButton}
73− disabled={!generated}
72+ disabled={!generated}
74− onClick={!downloaded ? download : goToVerify}
73+ onClick={!downloaded ? download : goToVerify}
75− success={downloaded}
74+ success={downloaded}
76− solid>
75+ solid>
77− {!downloaded ? 'Download Passport' : 'Continue'}
76+ {!downloaded ? 'Download Passport' : 'Continue'}
78− </Grid.Item>
77+ </Grid.Item>
78+ </Grid>
79+ </PassportView>
79 <PaperRenderer
80 <PaperRenderer
80 point={pointAsString}
81 point={pointAsString}
81 wallet={wallet}
82 wallet={wallet}
82 callback={data => setPaper(Just(data))}
83 callback={data => setPaper(Just(data))}
83 />
84 />
84− </Grid>
85+ </>
85 );
86 );
86}
87}
src/views/Activate/PassportTransfer.js+21−24
@@ -1,6 +1,5 @@
1import React, { useState, useCallback } from 'react';
1import React, { useState, useCallback } from 'react';
2−import cn from 'classnames';
3−import { Grid, H4, Text, ErrorText } from 'indigo-react';
2+import { Grid, Text, ErrorText } from 'indigo-react';
4
3
5import { useNetwork } from 'store/network';
4import { useNetwork } from 'store/network';
6import { useWallet } from 'store/wallet';
5import { useWallet } from 'store/wallet';
@@ -9,17 +8,17 @@ import { usePointCursor } from 'store/pointCursor';
9
8
10import * as need from 'lib/need';
9import * as need from 'lib/need';
11import useLifecycle from 'lib/useLifecycle';
10import useLifecycle from 'lib/useLifecycle';
12−import { claimPointFromInvite } from 'lib/invite';
11+import { reticketPointBetweenWallets } from 'lib/invite';
13import timeout from 'lib/timeout';
12import timeout from 'lib/timeout';
14import { fromWei } from 'lib/txn';
13import { fromWei } from 'lib/txn';
15
14
16−import Steps from 'components/Steps';
17import WarningBox from 'components/WarningBox';
15import WarningBox from 'components/WarningBox';
18import LoadingBar from 'components/LoadingBar';
16import LoadingBar from 'components/LoadingBar';
19import Highlighted from 'components/Highlighted';
17import Highlighted from 'components/Highlighted';
18+import { RestartButton } from 'components/Buttons';
20
19
21import { useActivateFlow } from './ActivateFlow';
20import { useActivateFlow } from './ActivateFlow';
22−import { RestartButton } from 'components/Buttons';
21+import PassportView from './PassportView';
23
22
24export default function PassportTransfer({ className, resetActivateRouter }) {
23export default function PassportTransfer({ className, resetActivateRouter }) {
25 const { replaceWith, names } = useHistory();
24 const { replaceWith, names } = useHistory();
@@ -92,9 +91,9 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
92 const _web3 = need.web3(web3);
91 const _web3 = need.web3(web3);
93 const _contracts = need.contracts(contracts);
92 const _contracts = need.contracts(contracts);
94
93
95− await claimPointFromInvite({
94+ await reticketPointBetweenWallets({
96− inviteWallet: _inviteWallet,
95+ fromWallet: _inviteWallet,
97− wallet: _wallet,
96+ toWallet: _wallet,
98 point: _point,
97 point: _point,
99 web3: _web3,
98 web3: _web3,
100 contracts: _contracts,
99 contracts: _contracts,
@@ -159,7 +158,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
159 );
158 );
160 }
159 }
161
160
162− if (progress < 100) {
161+ if (progress < 1.0) {
163 return (
162 return (
164 <Grid.Item full as={WarningBox} className="mt8">
163 <Grid.Item full as={WarningBox} className="mt8">
165 Never give your Master Ticket to anyone
164 Never give your Master Ticket to anyone
@@ -171,22 +170,20 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
171 };
170 };
172
171
173 return (
172 return (
174− <Grid className={cn(className, 'auto-rows-min')}>
173+ <PassportView className={className} header={label} step={3}>
175− <Grid.Item full as={Steps} num={3} total={3} />
176− <Grid.Item full as={H4}>
177− {label}
178− </Grid.Item>
174+ <Grid>
179− <Grid.Item full as={Grid} className="mt3" gap={3}>
175+ <Grid.Item full as={Grid} className="mt3" gap={3}>
180− <Grid.Item full as={LoadingBar} progress={progress} />
176+ <Grid.Item full as={LoadingBar} progress={progress} />
181− <Grid.Item full>
177+ <Grid.Item full>
182− <Text className="f5 green4">
178+ <Text className="f5 green4">
183− This process can take up to 5 minutes to complete. Don't leave this
179+ This process can take up to 5 minutes to complete. Don't leave
184− page until the process is complete.
180+ this page until the process is complete.
185− </Text>
181+ </Text>
182+ </Grid.Item>
186 </Grid.Item>
183 </Grid.Item>
187− </Grid.Item>
188
184
189− {renderAdditionalInfo()}
185+ {renderAdditionalInfo()}
190− </Grid>
186+ </Grid>
187+ </PassportView>
191 );
188 );
192}
189}
src/views/Activate/PassportVerify.js+23−25
@@ -1,18 +1,18 @@
1import React, { useCallback, useMemo } from 'react';
1import React, { useCallback, useMemo } from 'react';
2−import cn from 'classnames';
3−import { Grid, H4, Input, P } from 'indigo-react';
2+import { Grid, Input, P } from 'indigo-react';
4
3
5import * as need from 'lib/need';
4import * as need from 'lib/need';
6import { useLocalRouter } from 'lib/LocalRouter';
5import { useLocalRouter } from 'lib/LocalRouter';
7import { useTicketInput } from 'lib/useInputs';
6import { useTicketInput } from 'lib/useInputs';
8import { validateExactly } from 'lib/validators';
7import { validateExactly } from 'lib/validators';
8+import { isDevelopment } from 'lib/flags';
9
9
10import { ForwardButton } from 'components/Buttons';
10import { ForwardButton } from 'components/Buttons';
11−import Steps from 'components/Steps';
12
11
13import { useActivateFlow } from './ActivateFlow';
12import { useActivateFlow } from './ActivateFlow';
13+import PassportView from './PassportView';
14
14
15−const STUB_VERIFY_TICKET = process.env.NODE_ENV === 'development';
15+const STUB_VERIFY_TICKET = isDevelopment;
16
16
17export default function PassportVerify({ className }) {
17export default function PassportVerify({ className }) {
18 const { push, names } = useLocalRouter();
18 const { push, names } = useLocalRouter();
@@ -33,26 +33,24 @@ export default function PassportVerify({ className }) {
33 });
33 });
34
34
35 return (
35 return (
36− <Grid className={cn(className, 'auto-rows-min')}>
37− <Grid.Item full as={Steps} num={2} total={3} />
38− <Grid.Item full as={H4}>
39− Verify Passport
36+ <PassportView header="Verify Passport" step={2} className={className}>
40− </Grid.Item>
37+ <Grid>
41− <Grid.Item full as={P}>
38+ <Grid.Item full as={P}>
42− After you download your passport, verify your custody. Your passport
39+ After you download your passport, verify your custody. Your passport
43− should be a folder of image files. One of them is your Master Ticket.
40+ should be a folder of image files. One of them is your Master Ticket.
44− Open it and enter the 4 word phrase below (with hyphens).
41+ Open it and enter the 4 word phrase below (with hyphens).
45− </Grid.Item>
42+ </Grid.Item>
46− <Grid.Item full as={Input} {...ticketInput} />
43+ <Grid.Item full as={Input} {...ticketInput} />
47− <Grid.Item
44+ <Grid.Item
48− full
45+ full
49− className="mt3"
46+ className="mt3"
50− as={ForwardButton}
47+ as={ForwardButton}
51− disabled={!pass}
48+ disabled={!pass}
52− onClick={goToTransfer}
49+ onClick={goToTransfer}
53− solid>
50+ solid>
54− Verify
51+ Verify
55− </Grid.Item>
52+ </Grid.Item>
56− </Grid>
53+ </Grid>
54+ </PassportView>
57 );
55 );
58}
56}
src/views/Activate/PassportView.jsnew file+27
@@ -0,0 +1,27 @@
1+import React from 'react';
2+import cn from 'classnames';
3+import { Grid, H4 } from 'indigo-react';
4+
5+import Steps from 'components/Steps';
6+
7+// NOTE: the 48px matches the `mt8` class on Passport so things line up nicely
8+// TODO: there's probably a clever grid/flex way to do this instead
9+export default function PassportView({ className, children, header, step }) {
10+ return (
11+ <>
12+ <Grid className={cn(className, 'auto-rows-min')}>
13+ <Grid.Item
14+ full
15+ as={Steps}
16+ num={step}
17+ total={3}
18+ style={{ height: '48px' }}
19+ />
20+ <Grid.Item full as={H4}>
21+ {header}
22+ </Grid.Item>
23+ <Grid.Item full>{children}</Grid.Item>
24+ </Grid>
25+ </>
26+ );
27+}
src/views/Admin/AdminReticket.js+35−68
@@ -1,97 +1,64 @@
1−import React, { useState } from 'react';
1+import React, { useCallback, useState } from 'react';
2−import { size } from 'lodash';
3−import * as need from 'lib/need';
4−import ob from 'urbit-ob';
5import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3+import { Grid, Flex } from 'indigo-react';
6
4
7−import { usePointCursor } from 'store/pointCursor';
8−
9−import View from 'components/View';
10−import MiniBackButton from 'components/MiniBackButton';
11import { LocalRouterProvider } from 'lib/LocalRouter';
5import { LocalRouterProvider } from 'lib/LocalRouter';
12−import Crumbs from 'components/Crumbs';
13−
14import useRouter from 'lib/useRouter';
6import useRouter from 'lib/useRouter';
15
7
16−import { useHistory } from 'store/history';
8+import MiniBackButton from 'components/MiniBackButton';
9+import Steps from 'components/Steps';
17
10
18−import Confirm from '../Reticket/Confirm';
11+import ReticketConfirm from './Reticket/ReticketConfirm';
19−import Download from '../Reticket/Download';
12+import ReticketDownload from './Reticket/ReticketDownload';
20−import Verify from '../Reticket/Verify';
13+import ReticketVerify from './Reticket/ReticketVerify';
21−import DoReticket from '../Reticket/DoReticket';
14+import ReticketExecute from './Reticket/ReticketExecute';
22
15
23const NAMES = {
16const NAMES = {
24 CONFIRM: 'CONFIRM',
17 CONFIRM: 'CONFIRM',
25 DOWNLOAD: 'DOWNLOAD',
18 DOWNLOAD: 'DOWNLOAD',
26 VERIFY: 'VERIFY',
19 VERIFY: 'VERIFY',
27− RETICKET: 'RETICKET',
20+ EXECUTE: 'EXECUTE',
28};
21};
29
22
30const VIEWS = {
23const VIEWS = {
31− CONFIRM: Confirm,
24+ CONFIRM: ReticketConfirm,
32− DOWNLOAD: Download,
25+ DOWNLOAD: ReticketDownload,
33− VERIFY: Verify,
26+ VERIFY: ReticketVerify,
34− RETICKET: DoReticket,
27+ EXECUTE: ReticketExecute,
35};
28};
36
29
37−const STEP_MAX = size(NAMES);
30+const STEPS = [NAMES.CONFIRM, NAMES.DOWNLOAD, NAMES.VERIFY, NAMES.RETICKET];
38
31
39export default function AdminReticket() {
32export default function AdminReticket() {
40− const history = useHistory();
41− const { Route, ...innerRouter } = useRouter({
33+ const { Route, ...router } = useRouter({
42 names: NAMES,
34 names: NAMES,
43 views: VIEWS,
35 views: VIEWS,
44 initialRoutes: [{ key: NAMES.CONFIRM }],
36 initialRoutes: [{ key: NAMES.CONFIRM }],
45 });
37 });
46− const { pointCursor } = usePointCursor();
47− const point = need.point(pointCursor);
48− const name = ob.patp(point);
49
38
50− const [newWallet, setNewWallet] = useState(Nothing());
39+ const [newWallet, _setNewWallet] = useState(Nothing());
51
40
52− //TODO step # from Route?
53− const stepNumber = 1;
54−
55− const storeNewWallet = (wallet, paper) => {
56− setNewWallet(
41+ const setNewWallet = useCallback(
42+ (wallet, paper) => {
43+ _setNewWallet(
57− Just({
44+ Just({
58− wallet,
45+ wallet,
59− paper,
46+ paper,
60− })
47+ })
61− );
48+ );
62− };
49+ },
63−
50+ [_setNewWallet]
64− const completed = () => {
65− history.pop();
66− };
51+ );
67
52
68 return (
53 return (
69− <LocalRouterProvider value={innerRouter}>
54+ <LocalRouterProvider value={router}>
70− <View>
55+ <Grid>
71− <Crumbs
56+ <Grid.Item full as={Flex} row justify="between" align="center">
72− routes={[
73− {
74− text: name,
75− action: () => {
76− return history.pop(2);
77− },
78− },
79− {
80− text: 'Admin',
81− action: () => history.pop(),
82− },
83− ]}
84− />
85− <MiniBackButton onClick={innerRouter.pop} />
57+ <Flex.Item as={MiniBackButton} onClick={() => router.pop()} />
86− Step {stepNumber} of {STEP_MAX}
58+ <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
87− <Route
59+ </Grid.Item>
60+ </Grid>
88− newWallet={newWallet}
61+ <Route newWallet={newWallet} setNewWallet={setNewWallet} />
89− //
90− storeNewWallet={storeNewWallet}
91− //
92− completed={completed}
93− />
94− </View>
95 </LocalRouterProvider>
62 </LocalRouterProvider>
96 );
63 );
97}
64}
src/views/Admin/Reticket/ReticketConfirm.jsnew file+83
@@ -0,0 +1,83 @@
1+import React, { useState, useCallback } from 'react';
2+import { Just, Nothing } from 'folktale/maybe';
3+import { Grid, Text } from 'indigo-react';
4+import * as need from 'lib/need';
5+
6+import { usePointCursor } from 'store/pointCursor';
7+
8+import useLifecycle from 'lib/useLifecycle';
9+import { generateWallet } from 'lib/invite';
10+import { useLocalRouter } from 'lib/LocalRouter';
11+import { isDevelopment } from 'lib/flags';
12+
13+import { ForwardButton } from 'components/Buttons';
14+import PaperRenderer from 'components/PaperRenderer';
15+import WarningBox from 'components/WarningBox';
16+import Blinky from 'components/Blinky';
17+import useCurrentPointName from 'lib/useCurrentPointName';
18+
19+export default function ReticketConfirm({ newWallet, setNewWallet }) {
20+ const { push, names } = useLocalRouter();
21+ const { pointCursor } = usePointCursor();
22+ const point = need.point(pointCursor);
23+ const name = useCurrentPointName();
24+
25+ const [generatedWallet, setGeneratedWallet] = useState(Nothing());
26+
27+ // generating new wallet on mount, conveniently using it to 'force' users
28+ // to read the disclaimer text
29+ //TODO also check we can pay for the transactions?
30+ useLifecycle(() => {
31+ (async () => {
32+ if (Just.hasInstance(newWallet)) {
33+ // skip if already computed (hitting the back button for example)
34+ return;
35+ }
36+
37+ const wal = await generateWallet(point);
38+ if (isDevelopment) {
39+ console.log(`The new ticket for ${name} is ${wal.ticket}`);
40+ }
41+ setGeneratedWallet(Just(wal));
42+ })();
43+ });
44+
45+ const goDownload = useCallback(() => push(names.DOWNLOAD), [push, names]);
46+
47+ const paperRenderer = generatedWallet.matchWith({
48+ Nothing: () => null,
49+ Just: ({ value: wallet }) => (
50+ <PaperRenderer
51+ point={point}
52+ wallet={wallet}
53+ callback={paper => {
54+ setNewWallet(wallet, paper);
55+ }}
56+ />
57+ ),
58+ });
59+
60+ return (
61+ <Grid gap={4} className="mt4">
62+ <Grid.Item full as={Text}>
63+ Reticketing is the process of generating a completely fresh wallet and
64+ transferring ownership of your point to that wallet.
65+ </Grid.Item>
66+ <Grid.Item full as={WarningBox}>
67+ Beware, this resets your proxy addresses; if you're using smart
68+ contracts, this might break them! It will also change your networking
69+ keys!
70+ </Grid.Item>
71+ <Grid.Item
72+ full
73+ as={ForwardButton}
74+ solid
75+ accessory={Nothing.hasInstance(newWallet) ? <Blinky /> : undefined}
76+ disabled={Nothing.hasInstance(newWallet)}
77+ onClick={goDownload}>
78+ I understand, continue
79+ </Grid.Item>
80+ {paperRenderer}
81+ </Grid>
82+ );
83+}
src/views/Admin/Reticket/ReticketDownload.jsnew file+38
@@ -0,0 +1,38 @@
1+import React, { useState, useCallback } from 'react';
2+import { Nothing } from 'folktale/maybe';
3+import { Grid, Text } from 'indigo-react';
4+
5+import { useLocalRouter } from 'lib/LocalRouter';
6+import { downloadWallet } from 'lib/invite';
7+
8+import { ForwardButton, DownloadButton } from 'components/Buttons';
9+
10+export default function ReticketDownload({ newWallet }) {
11+ const { push, names } = useLocalRouter();
12+
13+ const [downloaded, setDownloaded] = useState(false);
14+
15+ const download = () => {
16+ downloadWallet(newWallet.value.paper);
17+ setDownloaded(true);
18+ };
19+
20+ const goVerify = useCallback(() => push(names.VERIFY), [push, names]);
21+
22+ return (
23+ <Grid gap={4} className="mt4">
24+ <Grid.Item full as={Text}>
25+ Download the thing, and keep it somewhere safe!
26+ </Grid.Item>
27+ <Grid.Item
28+ full
29+ as={!downloaded ? DownloadButton : ForwardButton}
30+ disabled={Nothing.hasInstance(newWallet)}
31+ onClick={!downloaded ? download : goVerify}
32+ success={downloaded}
33+ solid>
34+ {!downloaded ? 'Download Passport' : 'Continue'}
35+ </Grid.Item>
36+ </Grid>
37+ );
38+}
src/views/Reticket/DoReticket.js → src/views/Admin/Reticket/ReticketExecute.jsrenamed+68−39
@@ -1,25 +1,28 @@
1import React, { useState, useCallback } from 'react';
1import React, { useState, useCallback } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3−
4import { Grid, H4, Text, ErrorText } from 'indigo-react';
3import { Grid, H4, Text, ErrorText } from 'indigo-react';
5−import { RestartButton } from 'components/Buttons';
6−import WarningBox from 'components/WarningBox';
7−import LoadingBar from 'components/LoadingBar';
8−import Highlighted from 'components/Highlighted';
9
4
10−import { claimPointFromInvite } from 'lib/invite';
5+import { reticketPointBetweenWallets } from 'lib/invite';
11import { fromWei } from 'lib/txn';
6import { fromWei } from 'lib/txn';
12−
13import * as need from 'lib/need';
7import * as need from 'lib/need';
8+import useLifecycle from 'lib/useLifecycle';
9+
14import { useNetwork } from 'store/network';
10import { useNetwork } from 'store/network';
15import { useWallet } from 'store/wallet';
11import { useWallet } from 'store/wallet';
16import { usePointCursor } from 'store/pointCursor';
12import { usePointCursor } from 'store/pointCursor';
17−import useLifecycle from 'lib/useLifecycle';
13+import { useHistory } from 'store/history';
18
14
15+import { RestartButton, ForwardButton } from 'components/Buttons';
16+import WarningBox from 'components/WarningBox';
17+import LoadingBar from 'components/LoadingBar';
18+import Highlighted from 'components/Highlighted';
19+import { WALLET_TYPES } from 'lib/wallet';
20+
19−//TODO maybe we want to drop in PassportTransfer here?
21+// TODO: maybe we can merge PassportTransfer here?
20−export default function DoReticket({ newWallet, completed }) {
22+export default function ReticketExecute({ newWallet }) {
23+ const { popTo, names } = useHistory();
21 const { web3, contracts } = useNetwork();
24 const { web3, contracts } = useNetwork();
22− const { wallet, setUrbitWallet } = useWallet();
25+ const { wallet, setWalletType, resetWallet, setUrbitWallet } = useWallet();
23 const { pointCursor } = usePointCursor();
26 const { pointCursor } = usePointCursor();
24
27
25 const [generalError, setGeneralError] = useState();
28 const [generalError, setGeneralError] = useState();
@@ -28,28 +31,36 @@ export default function DoReticket({ newWallet, completed }) {
28 progress: 0,
31 progress: 0,
29 });
32 });
30 const [needFunds, setNeedFunds] = useState();
33 const [needFunds, setNeedFunds] = useState();
34+ const isDone = progress >= 1.0;
31
35
32 // start reticketing transactions on mount
36 // start reticketing transactions on mount
33 useLifecycle(() => {
37 useLifecycle(() => {
34 (async () => {
38 (async () => {
35 try {
39 try {
36− await claimPointFromInvite({
40+ await reticketPointBetweenWallets({
37− inviteWallet: need.wallet(wallet),
41+ fromWallet: need.wallet(wallet),
38− wallet: newWallet.value.wallet,
42+ toWallet: newWallet.value.wallet,
39 point: need.point(pointCursor),
43 point: need.point(pointCursor),
40 web3: need.web3(web3),
44 web3: need.web3(web3),
41 contracts: need.contracts(contracts),
45 contracts: need.contracts(contracts),
42 onUpdate: handleUpdate,
46 onUpdate: handleUpdate,
43 });
47 });
44− //TODO would it be better for this to happen in completed()?
45− setUrbitWallet(Just(newWallet.value.wallet));
46− completed(); //TODO don't auto-redirect
47 } catch (err) {
48 } catch (err) {
48 setGeneralError(err);
49 setGeneralError(err);
49 }
50 }
50 })();
51 })();
51 });
52 });
52
53
54+ const loginAndGoHome = useCallback(() => {
55+ // set wallet state
56+ resetWallet();
57+ setWalletType(WALLET_TYPES.TICKET);
58+ setUrbitWallet(Just(newWallet.value.wallet));
59+ // (implicit) pointCursor stays the same
60+ // rediect to point
61+ popTo(names.POINT);
62+ }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
63+
53 const handleUpdate = useCallback(
64 const handleUpdate = useCallback(
54 ({ type, state, value }) => {
65 ({ type, state, value }) => {
55 switch (type) {
66 switch (type) {
@@ -70,7 +81,7 @@ export default function DoReticket({ newWallet, completed }) {
70 if (generalError) {
81 if (generalError) {
71 return (
82 return (
72 <>
83 <>
73− <Grid.Item full className="mt8">
84+ <Grid.Item full>
74 <ErrorText>{generalError.message.toString()}</ErrorText>
85 <ErrorText>{generalError.message.toString()}</ErrorText>
75 </Grid.Item>
86 </Grid.Item>
76 <Grid.Item
87 <Grid.Item
@@ -85,7 +96,7 @@ export default function DoReticket({ newWallet, completed }) {
85
96
86 if (needFunds) {
97 if (needFunds) {
87 return (
98 return (
88− <Grid.Item full className="mt8">
99+ <Grid.Item full>
89 <Highlighted>
100 <Highlighted>
90 The address {needFunds.address} needs at least{' '}
101 The address {needFunds.address} needs at least{' '}
91 {fromWei(needFunds.minBalance)} ETH and currently has{' '}
102 {fromWei(needFunds.minBalance)} ETH and currently has{' '}
@@ -96,33 +107,51 @@ export default function DoReticket({ newWallet, completed }) {
96 );
107 );
97 }
108 }
98
109
99− if (progress < 100) {
100− return (
110+ return (
101− <Grid.Item full as={WarningBox} className="mt8">
111+ <Grid.Item full as={WarningBox}>
102− Never give your Master Ticket to anyone
112+ Never give your Master Ticket to anyone
103− </Grid.Item>
113+ </Grid.Item>
104− );
114+ );
105− }
106−
107− return null;
108 };
115 };
109
116
110 return (
117 return (
111− <Grid className={'auto-rows-min'}>
118+ <Grid gap={4} className="mt4">
112 <Grid.Item full as={H4}>
119 <Grid.Item full as={H4}>
113 {label}
120 {label}
114 </Grid.Item>
121 </Grid.Item>
115− <Grid.Item full as={Grid} className="mt3" gap={3}>
116− <Grid.Item full as={LoadingBar} progress={progress} />
117− <Grid.Item full>
118− <Text className="f5 green4">
119− This process can take up to 5 minutes to complete. Don't leave this
120− page until the process is complete.
121− </Text>
122− </Grid.Item>
123− </Grid.Item>
124
122
123+ {!isDone && (
124+ <>
125+ <Grid.Item full as={Grid} className="mt3" gap={3}>
126+ <Grid.Item full as={LoadingBar} progress={progress} />
127+ <Grid.Item full>
128+ <Text className="f5 green4">
129+ This process can take up to 5 minutes to complete. Don't leave
130+ this page until the process is complete.
131+ </Text>
132+ </Grid.Item>
133+ </Grid.Item>
134+
125− {renderAdditionalInfo()}
135+ {renderAdditionalInfo()}
136+ </>
137+ )}
138+
139+ {isDone && (
140+ <>
141+ <Grid.Item full as={Text}>
142+ Your changes are now reflected on-chain and you can use the new
143+ ticket to manage your point.
144+ </Grid.Item>
145+ <Grid.Item
146+ full
147+ as={ForwardButton}
148+ solid
149+ accessory="↺"
150+ onClick={loginAndGoHome}>
151+ Login with New Master Ticket
152+ </Grid.Item>
153+ </>
154+ )}
126 </Grid>
155 </Grid>
127 );
156 );
128}
157}
src/views/Admin/Reticket/ReticketVerify.js+2−2
@@ -1,13 +1,13 @@
1import React, { useCallback, useMemo } from 'react';
1import React, { useCallback, useMemo } from 'react';
2import { Input, Text, Grid } from 'indigo-react';
2import { Input, Text, Grid } from 'indigo-react';
3
3
4−import { ForwardButton } from 'components/Buttons';
5−
6import { useTicketInput } from 'lib/useInputs';
4import { useTicketInput } from 'lib/useInputs';
7import { useLocalRouter } from 'lib/LocalRouter';
5import { useLocalRouter } from 'lib/LocalRouter';
8import { validateExactly } from 'lib/validators';
6import { validateExactly } from 'lib/validators';
9import { isDevelopment } from 'lib/flags';
7import { isDevelopment } from 'lib/flags';
10
8
9+import { ForwardButton } from 'components/Buttons';
10+
11const STUB_VERIFY_TICKET = isDevelopment;
11const STUB_VERIFY_TICKET = isDevelopment;
12
12
13export default function ReticketVerify({ newWallet }) {
13export default function ReticketVerify({ newWallet }) {
src/views/Point.js+12−19
@@ -16,33 +16,26 @@ import { matchBlinky } from 'components/Blinky';
16import useInvites from 'lib/useInvites';
16import useInvites from 'lib/useInvites';
17import { useSyncOwnedPoints } from 'lib/useSyncPoints';
17import { useSyncOwnedPoints } from 'lib/useSyncPoints';
18import { ROUTE_NAMES } from 'lib/routeNames';
18import { ROUTE_NAMES } from 'lib/routeNames';
19−import { eqAddr } from 'lib/wallet';
20
19
21import Actions from './Point/Actions';
20import Actions from './Point/Actions';
22import { useWallet } from 'store/wallet';
21import { useWallet } from 'store/wallet';
22+import usePermissionsForPoint from 'lib/usePermissionsForPoint';
23
23
24export default function Point() {
24export default function Point() {
25 const history = useHistory();
25 const history = useHistory();
26 const { pointCursor } = usePointCursor();
26 const { pointCursor } = usePointCursor();
27− const { pointCache } = usePointCache();
28 const { wallet } = useWallet();
27 const { wallet } = useWallet();
29
28
30 const point = need.point(pointCursor);
29 const point = need.point(pointCursor);
31
30
32− let canAdmin = false;
31+ const { canManage } = usePermissionsForPoint(
33− if (point in pointCache) {
32+ // using empty string should be ok here
34− canAdmin = wallet.matchWith({
33+ wallet.matchWith({
35− Nothing: () => false,
34+ Nothing: () => '',
36− Just: wal => {
35+ Just: p => p.value.address,
37− const pointDetails = need.fromPointCache(pointCache, point);
38− const address = need.addressFromWallet(wallet);
39− return (
40− eqAddr(address, pointDetails.owner) ||
41− eqAddr(address, pointDetails.managementProxy)
42− );
43− },
36+ }),
44− });
37+ point
45− }
38+ );
46
39
47 // fetch the invites for the current cursor
40 // fetch the invites for the current cursor
48 const { availableInvites } = useInvites(point);
41 const { availableInvites } = useInvites(point);
@@ -66,7 +59,7 @@ export default function Point() {
66 <Grid.Item
59 <Grid.Item
67 full
60 full
68 as={ForwardButton}
61 as={ForwardButton}
69− disabled={!canAdmin}
62+ disabled={!canManage}
70 onClick={goAdmin}>
63 onClick={goAdmin}>
71 Admin
64 Admin
72 </Grid.Item>
65 </Grid.Item>
@@ -81,8 +74,8 @@ export default function Point() {
81 )}
74 )}
82 </Grid>
75 </Grid>
83
76
84− <FooterButton onClick={goInvite}>
77+ <FooterButton disabled={!canManage} onClick={goInvite}>
85− Invite <sup>{availableInvitesText}</sup>
78+ Invite <sup>{availableInvitesText} available</sup>
86 </FooterButton>
79 </FooterButton>
87 </View>
80 </View>
88 );
81 );
src/views/Reticket/Confirm.jsdeleted−63
@@ -1,63 +0,0 @@
1−import React, { useState, useCallback } from 'react';
2−import * as need from 'lib/need';
3−import { Just, Nothing } from 'folktale/maybe';
4−
5−import { usePointCursor } from 'store/pointCursor';
6−
7−import View from 'components/View';
8−import { ForwardButton } from 'components/Buttons';
9−import PaperRenderer from 'components/PaperRenderer';
10−
11−import useLifecycle from 'lib/useLifecycle';
12−import { generateWallet } from 'lib/invite';
13−import { useLocalRouter } from 'lib/LocalRouter';
14−
15−import { isDevelopment } from 'lib/flags';
16−
17−export default function Confirm({ newWallet, storeNewWallet }) {
18− const { push, names } = useLocalRouter();
19− const { pointCursor } = usePointCursor();
20− const point = need.point(pointCursor);
21−
22− const [generatedWallet, setGeneratedWallet] = useState(Nothing());
23−
24− // start generating new wallet on mount
25− //TODO also check we can pay for the transactions?
26− useLifecycle(() => {
27− (async () => {
28− const wal = await generateWallet(point);
29− if (isDevelopment) {
30− console.log('ticket for', point, wal.ticket);
31− }
32− setGeneratedWallet(Just(wal));
33− })();
34− });
35−
36− const next = useCallback(() => push(names.DOWNLOAD), [push, names]);
37−
38− const paperRenderer = generatedWallet.matchWith({
39− Nothing: () => null,
40− Just: gw => (
41− <PaperRenderer
42− point={point}
43− wallet={gw.value}
44− callback={paper => {
45− storeNewWallet(gw.value, paper);
46− }}
47− />
48− ),
49− });
50−
51− return (
52− <View>
53− Maybe we should just duplicate the Login component here? Not everyone who
54− does this will be logged in with an existing ticket. Also, beware, this
55− changes your proxy addresses. If you're using smart contracts, this might
56− break them! It will also change your networking keys!
57− <ForwardButton disabled={Nothing.hasInstance(newWallet)} onClick={next}>
58− I understand, continue
59− </ForwardButton>
60− {paperRenderer}
61− </View>
62− );
63−}
src/views/Reticket/Download.jsdeleted−35
@@ -1,35 +0,0 @@
1−import React, { useState, useCallback } from 'react';
2−import { Nothing } from 'folktale/maybe';
3−
4−import View from 'components/View';
5−import { ForwardButton, DownloadButton } from 'components/Buttons';
6−
7−import { useLocalRouter } from 'lib/LocalRouter';
8−import { downloadWallet } from 'lib/invite';
9−
10−export default function Download({ newWallet }) {
11− const { push, names } = useLocalRouter();
12−
13− const [hasDownloaded, setHasDownloaded] = useState(false);
14−
15− const doDownload = () => {
16− downloadWallet(newWallet.value.paper);
17− setHasDownloaded(true);
18− };
19−
20− const next = useCallback(() => push(names.VERIFY), [push, names]);
21−
22− return (
23− <View>
24− Download the thing, and keep it somewhere safe!
25− <DownloadButton
26− disabled={Nothing.hasInstance(newWallet)}
27− onClick={doDownload}>
28− Download
29− </DownloadButton>
30− <ForwardButton disabled={!hasDownloaded} onClick={next}>
31− Continue
32− </ForwardButton>
33− </View>
34− );
35−}
src/views/Reticket/Verify.jsdeleted−41
@@ -1,41 +0,0 @@
1−import React, { useCallback, useMemo } from 'react';
2−import { Input } from 'indigo-react';
3−
4−import { useTicketInput } from 'lib/useInputs';
5−import { useLocalRouter } from 'lib/LocalRouter';
6−import { validateExactly } from 'lib/validators';
7−
8−import View from 'components/View';
9−import { ForwardButton } from 'components/Buttons';
10−
11−const STUB_VERIFY_TICKET = process.env.NODE_ENV === 'development';
12−
13−//TODO deduplicate with PassportVerify
14−export default function Verify({ newWallet }) {
15− const { push, names } = useLocalRouter();
16−
17− const ticket = newWallet.value.wallet.ticket;
18− const validators = useMemo(
19− () => [validateExactly(ticket, 'Does not match expected master ticket.')],
20− [ticket]
21− );
22− const [ticketInput, { pass }] = useTicketInput({
23− name: 'ticket',
24− label: 'New master ticket',
25− initialValue: STUB_VERIFY_TICKET ? ticket : undefined,
26− autoFocus: true,
27− validators,
28− });
29−
30− const next = useCallback(() => push(names.RETICKET), [push, names]);
31−
32− return (
33− <View>
34− Prove that you downloaded the new secrets!
35− <Input {...ticketInput} />
36− <ForwardButton disabled={!pass} onClick={next}>
37− Reticket
38− </ForwardButton>
39− </View>
40− );
41−}