on master help sign in
checkpoint: initial code for InlineEthereumTransaction, useEthereumTransaction
06b6de4Matt Condon (shrugs)
src/components/Buttons.js+4−1
@@ -1,8 +1,11 @@
1import React from 'react';
1import React from 'react';
2import { Button } from 'indigo-react';
2import { Button } from 'indigo-react';
3+import Blinky from './Blinky';
3
4
4// NOTE: the -> is correct because inter recognizes the pair
5// NOTE: the -> is correct because inter recognizes the pair
5−export const ForwardButton = props => <Button accessory="->" {...props} />;
6+export const ForwardButton = ({ loading, ...props }) => (
7+ <Button accessory={loading ? <Blinky /> : '->'} {...props} />
8+);
6export const DownloadButton = props => <Button accessory="↓" {...props} />;
9export const DownloadButton = props => <Button accessory="↓" {...props} />;
7export const RestartButton = props => <Button accessory="↺" solid {...props} />;
10export const RestartButton = props => <Button accessory="↺" solid {...props} />;
8export const GenerateButton = props => (
11export const GenerateButton = props => (
src/components/InlineEthereumTransaction.jsnew file+94
@@ -0,0 +1,94 @@
1+import React from 'react';
2+import { Grid, ToggleInput, ErrorText } from 'indigo-react';
3+import { GenerateButton, ForwardButton } from './Buttons';
4+import { useCheckboxInput } from 'lib/useInputs';
5+import Blinky from './Blinky';
6+
7+export default function InlineEthereumTransaction({
8+ initializing,
9+ construct,
10+ constructed,
11+ canSign,
12+ generateAndSign,
13+ signed,
14+ broadcast,
15+ broadcasted,
16+ confirmed,
17+ reset,
18+ error,
19+ setGasPrice,
20+ className,
21+}) {
22+ const [configureInput, { data: configurationOpen }] = useCheckboxInput({
23+ name: 'configure',
24+ label: 'Configure Transaction',
25+ disabled: signed || broadcasted || confirmed,
26+ });
27+
28+ const [
29+ viewSignedTransaction,
30+ { data: signedTransactionOpen },
31+ ] = useCheckboxInput({
32+ name: 'viewsigned',
33+ label: 'View Signed Transaction',
34+ disabled: !signed,
35+ });
36+
37+ const renderButton = () => {
38+ if (confirmed) {
39+ // some sort of next button or nothing at all
40+ return null;
41+ } else if (broadcasted) {
42+ return (
43+ <Grid.Item
44+ full
45+ as={ForwardButton}
46+ solid
47+ success
48+ disabled
49+ accessory={<Blinky />}>
50+ Sending Transaction...
51+ </Grid.Item>
52+ );
53+ } else if (signed) {
54+ return (
55+ <Grid.Item
56+ full
57+ as={ForwardButton}
58+ solid
59+ success
60+ onClick={() => broadcast()}>
61+ Send Transaction
62+ </Grid.Item>
63+ );
64+ } else {
65+ return (
66+ <Grid.Item
67+ full
68+ as={GenerateButton}
69+ onClick={generateAndSign}
70+ disabled={!canSign}
71+ accessory={!canSign && initializing ? <Blinky /> : undefined}>
72+ Generate & Sign Transaction
73+ </Grid.Item>
74+ );
75+ }
76+ };
77+
78+ return (
79+ <Grid className={className}>
80+ {renderButton()}
81+
82+ {error && (
83+ <Grid.Item full as={ErrorText} className="mt1">
84+ {error.message}
85+ </Grid.Item>
86+ )}
87+ <Grid.Item full as={ToggleInput} {...configureInput} />
88+
89+ {!broadcasted && !confirmed && (
90+ <Grid.Item full as={ToggleInput} {...viewSignedTransaction} />
91+ )}
92+ </Grid>
93+ );
94+}
src/indigo-react/components/CheckboxInput.js−2
@@ -3,7 +3,6 @@ import cn from 'classnames';
3
3
4import Flex from './Flex';
4import Flex from './Flex';
5
5
6−// TODO: make this look like screens
7export default function CheckboxInput({
6export default function CheckboxInput({
8 // visuals
7 // visuals
9 name,
8 name,
@@ -11,7 +10,6 @@ export default function CheckboxInput({
11 className,
10 className,
12
11
13 // callbacks
12 // callbacks
14− onValue,
15 onEnter,
13 onEnter,
16
14
17 // state from hook
15 // state from hook
src/indigo-react/components/Input.js+9−12
@@ -1,4 +1,4 @@
1−import React, { useCallback, useEffect } from 'react';
1+import React, { useCallback } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3
3
4import Flex from './Flex';
4import Flex from './Flex';
@@ -13,7 +13,6 @@ export default function Input({
13 mono = false,
13 mono = false,
14
14
15 // callbacks
15 // callbacks
16− onValue,
17 onEnter,
16 onEnter,
18
17
19 // state from hook
18 // state from hook
@@ -46,14 +45,6 @@ export default function Input({
46 [disabled, pass] // eslint-disable-line react-hooks/exhaustive-deps
45 [disabled, pass] // eslint-disable-line react-hooks/exhaustive-deps
47 );
46 );
48
47
49− // notify parent of value only when passing
50− // TODO: deprecate/remove this
51− // keeping it around for now to avoid refactoring InviteEmail.js, but this
52− // feels like a hack
53− useEffect(() => {
54− pass && onValue && onValue(data);
55− }, [pass, data]); // eslint-disable-line react-hooks/exhaustive-deps
56−
57 return (
48 return (
58 <Flex
49 <Flex
59 className={cn(className, 'mb1')}
50 className={cn(className, 'mb1')}
@@ -64,7 +55,13 @@ export default function Input({
64 cursor: 'not-allowed',
55 cursor: 'not-allowed',
65 }),
56 }),
66 }}>
57 }}>
58+ <Flex.Item
59+ as="label"
67− <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
60+ className={cn('f6 lh-tall', {
61+ black: !disabled,
62+ gray4: disabled,
63+ })}
64+ htmlFor={name}>
68 {label}
65 {label}
69 </Flex.Item>
66 </Flex.Item>
70 <Flex.Item as={Flex} className="rel" row>
67 <Flex.Item as={Flex} className="rel" row>
@@ -86,7 +83,7 @@ export default function Input({
86 'b-green3': visiblyPassed,
83 'b-green3': visiblyPassed,
87 'b-black': focused && !visiblyPassed,
84 'b-black': focused && !visiblyPassed,
88 'b-yellow3': !focused && hintError,
85 'b-yellow3': !focused && hintError,
89− 'b-gray3': !focused && !hintError && !visiblyPassed,
86+ 'b-gray2': !focused && !hintError && !visiblyPassed,
90 }
87 }
91 // TODO: inputClassName ?
88 // TODO: inputClassName ?
92 )}
89 )}
src/indigo-react/components/LinkButton.js+3−5
@@ -3,7 +3,6 @@ import cn from 'classnames';
3
3
4export default function LinkButton({
4export default function LinkButton({
5 as: As = 'a',
5 as: As = 'a',
6− solid = false,
7 disabled = false,
6 disabled = false,
8 className,
7 className,
9 onClick,
8 onClick,
@@ -20,11 +19,10 @@ export default function LinkButton({
20 }),
19 }),
21 }}
20 }}
22 className={cn(
21 className={cn(
23− 'pointer ph2 underline',
22+ 'pointer underline',
24 {
23 {
25− white: solid,
26− black: !solid && !disabled,
24+ black: !disabled,
27− gray4: !solid && disabled,
25+ gray4: disabled,
28 },
26 },
29 className
27 className
30 )}
28 )}
src/indigo-react/components/SelectInput.js−1
@@ -17,7 +17,6 @@ export default function SelectInput({
17 placeholder,
17 placeholder,
18
18
19 // callbacks
19 // callbacks
20− onValue,
21 onEnter,
20 onEnter,
22
21
23 // state from hook
22 // state from hook
src/indigo-react/components/ToggleInput.jsnew file+68
@@ -0,0 +1,68 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+import Flex from './Flex';
5+import LinkButton from './LinkButton';
6+
7+export default function ToggleInput({
8+ // visuals
9+ name,
10+ label,
11+ className,
12+
13+ // callbacks
14+ onEnter,
15+
16+ // state from hook
17+ focused,
18+ pass,
19+ visiblyPassed,
20+ error,
21+ hintError,
22+ data,
23+ bind,
24+ autoFocus,
25+ disabled,
26+
27+ // ignored
28+ initialValue,
29+ validators,
30+ transformers,
31+
32+ ...rest
33+}) {
34+ return (
35+ <Flex
36+ className={cn(className)}
37+ row
38+ align="center"
39+ style={{
40+ ...(disabled && {
41+ pointerEvents: 'none',
42+ cursor: 'not-allowed',
43+ }),
44+ }}>
45+ {/* we totally hide the checkbox itself */}
46+ <Flex.Item
47+ as="input"
48+ {...rest}
49+ className={cn('super-hidden')}
50+ id={name}
51+ name={name}
52+ {...bind}
53+ />
54+ {/* and then display a prettier one in its stead */}
55+ <Flex.Item
56+ flex
57+ as="label"
58+ className={cn('f6 pv1 lh-tall us-none flex-row align-center', {
59+ clickable: !disabled,
60+ })}
61+ htmlFor={name}>
62+ <LinkButton disabled={disabled} className="f5">
63+ {bind.checked ? `Hide ${label}` : label}
64+ </LinkButton>
65+ </Flex.Item>
66+ </Flex>
67+ );
68+}
src/indigo-react/index.js+1
@@ -4,6 +4,7 @@ export { default as Flex } from './components/Flex';
4export { default as Input } from './components/Input';
4export { default as Input } from './components/Input';
5export { default as SelectInput } from './components/SelectInput';
5export { default as SelectInput } from './components/SelectInput';
6export { default as CheckboxInput } from './components/CheckboxInput';
6export { default as CheckboxInput } from './components/CheckboxInput';
7+export { default as ToggleInput } from './components/ToggleInput';
7export { default as AccessoryIcon } from './components/AccessoryIcon';
8export { default as AccessoryIcon } from './components/AccessoryIcon';
8export { default as Button } from './components/Button';
9export { default as Button } from './components/Button';
9export { default as IconButton } from './components/IconButton';
10export { default as IconButton } from './components/IconButton';
src/indigo-react/lib/useForm.js+11−7
@@ -192,11 +192,16 @@ export default function useForm(inputConfigs = []) {
192 configs.map(
192 configs.map(
193 ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => {
193 ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => {
194 const value =
194 const value =
195+ datas[i] !== undefined
196+ ? datas[i]
195− values[name] || (initialValue === undefined ? '' : initialValue);
197+ : initialValue !== undefined
198+ ? initialValue
199+ : '';
196 return {
200 return {
197 // Input props
201 // Input props
198 name,
202 name,
203+ value: value,
199− data: datas[i],
204+ data: passes[i] ? datas[i] : undefined,
200 pass: passes[i],
205 pass: passes[i],
201 visiblyPassed: visiblePasses[i],
206 visiblyPassed: visiblePasses[i],
202 error: errors[i],
207 error: errors[i],
@@ -205,10 +210,10 @@ export default function useForm(inputConfigs = []) {
205 autoFocus: autoFocus && !disabled,
210 autoFocus: autoFocus && !disabled,
206 disabled,
211 disabled,
207 ...rest,
212 ...rest,
208− // dom properties below:
213+ // DOM Properties for <input />
209 bind: {
214 bind: {
210 value,
215 value,
211− checked: !!values[name],
216+ checked: !!datas[i],
212 onChange: onChange(name),
217 onChange: onChange(name),
213 onFocus: onFocus(name),
218 onFocus: onFocus(name),
214 onBlur: onBlur(name),
219 onBlur: onBlur(name),
@@ -220,15 +225,14 @@ export default function useForm(inputConfigs = []) {
220 [
225 [
221 configs,
226 configs,
222 datas,
227 datas,
223− visiblePasses,
224 passes,
228 passes,
229+ visiblePasses,
225 errors,
230 errors,
226 hintErrors,
231 hintErrors,
227 focuses,
232 focuses,
228− values,
229− onBlur,
230 onChange,
233 onChange,
231 onFocus,
234 onFocus,
235+ onBlur,
232 ]
236 ]
233 );
237 );
234
238
src/lib/useEthereumTransaction.js+41−36
@@ -15,7 +15,6 @@ import { toHex, toWei, fromWei } from 'web3-utils';
15
15
16const STATE = {
16const STATE = {
17 NONE: 'NONE',
17 NONE: 'NONE',
18− GENERATED: 'GENERATED',
19 SIGNED: 'SIGNED',
18 SIGNED: 'SIGNED',
20 BROADCASTED: 'BROADCASTED',
19 BROADCASTED: 'BROADCASTED',
21 CONFIRMED: 'CONFIRMED',
20 CONFIRMED: 'CONFIRMED',
@@ -23,13 +22,9 @@ const STATE = {
23
22
24/**
23/**
25 * manage the state around sending and confirming an ethereum transaction
24 * manage the state around sending and confirming an ethereum transaction
26− * @param {Maybe<EthereumTx>} unsignedTx
27 * @param {number} initialGasLimit
25 * @param {number} initialGasLimit
28 */
26 */
29−export default function useEthereumTransaction(
27+export default function useEthereumTransaction(initialGasLimit = 0) {
30− unsignedTx,
31− initialGasLimit = 0
32−) {
33 const { wallet, walletType, walletHdPath } = useWallet();
28 const { wallet, walletType, walletHdPath } = useWallet();
34 const { web3, networkType } = useNetwork();
29 const { web3, networkType } = useNetwork();
35 const _web3 = need.web3(web3);
30 const _web3 = need.web3(web3);
@@ -45,48 +40,51 @@ export default function useEthereumTransaction(
45 const [signedTransaction, setSignedTransaction] = useState();
40 const [signedTransaction, setSignedTransaction] = useState();
46
41
47 const initializing = nonce === undefined || chainId === undefined;
42 const initializing = nonce === undefined || chainId === undefined;
48− const constructed = Just.hasInstance(unsignedTx);
43+ const constructed = !!unsignedTransaction;
49− const generated = state === STATE.GENERATED;
50 const signed = state === STATE.SIGNED;
44 const signed = state === STATE.SIGNED;
51 const broadcasted = state === STATE.BROADCASTED;
45 const broadcasted = state === STATE.BROADCASTED;
52 const confirmed = state === STATE.CONFIRMED;
46 const confirmed = state === STATE.CONFIRMED;
53
47
54− const generate = useCallback(() => {
48+ const inputsLocked = signed || broadcasted || confirmed;
55− const txn = unsignedTx.value;
56− try {
57− txn.gas = toHex(gasLimit);
58− txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
59
49
60− setUnsignedTransaction(txn);
50+ const canSign = !initializing && constructed;
61− setState(STATE.GENERATED);
51+
62− } catch (error) {
52+ const construct = useCallback(txn => setUnsignedTransaction(txn), [
63− setError(error);
53+ setUnsignedTransaction,
64− }
54+ ]);
65− }, [gasLimit, gasPrice, unsignedTx.value]);
66
55
67− const sign = useCallback(async () => {
56+ const generateAndSign = useCallback(async () => {
68 try {
57 try {
69− // TODO: sign the transaction
58+ // TODO: set varaibles
59+ // txn.gas = toHex(gasLimit);
60+ // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
61+
70− const txn = await signTransaction(
62+ const txn = await signTransaction({
71− _wallet,
63+ wallet, // wallet: _wallet,
72 walletType,
64 walletType,
73− walletHdPath.getOrElse(''),
65+ walletHdPath,
74 networkType,
66 networkType,
67+ txn: Just(unsignedTransaction),
68+ setStx: () => {}, // TODO: remove
69+ nonce,
75 chainId,
70 chainId,
76− unsignedTransaction
71+ gasPrice,
77− );
72+ gasLimit,
78− const stx = hexify(txn.serialize());
73+ });
79
74
80− setSignedTransaction(stx);
75+ setSignedTransaction(txn);
81 setState(STATE.SIGNED);
76 setState(STATE.SIGNED);
82 } catch (error) {
77 } catch (error) {
83 setError(error);
78 setError(error);
84 }
79 }
85 }, [
80 }, [
86− _wallet,
87 chainId,
81 chainId,
82+ gasLimit,
83+ gasPrice,
88 networkType,
84 networkType,
85+ nonce,
89 unsignedTransaction,
86 unsignedTransaction,
87+ wallet,
90 walletHdPath,
88 walletHdPath,
91 walletType,
89 walletType,
92 ]);
90 ]);
@@ -105,7 +103,11 @@ export default function useEthereumTransaction(
105
103
106 const broadcast = useCallback(async () => {
104 const broadcast = useCallback(async () => {
107 try {
105 try {
108− const txHash = await sendSignedTransaction(_web3, signedTransaction);
106+ const txHash = await sendSignedTransaction(
107+ _web3,
108+ Just(signedTransaction),
109+ /* doubtNonceError */ false
110+ );
109
111
110 setState(STATE.BROADCASTED);
112 setState(STATE.BROADCASTED);
111 waitForConfirmation(txHash);
113 waitForConfirmation(txHash);
@@ -141,24 +143,27 @@ export default function useEthereumTransaction(
141
143
142 return {
144 return {
143 initializing,
145 initializing,
146+ construct,
144 constructed,
147 constructed,
145− generate,
148+ canSign,
146− generated,
149+ generateAndSign,
147− sign,
148 signed,
150 signed,
149 broadcast,
151 broadcast,
150 broadcasted,
152 broadcasted,
151 confirmed,
153 confirmed,
152 reset,
154 reset,
153 error,
155 error,
156+ inputsLocked,
154 bind: {
157 bind: {
155− error,
156− initializing,
157 constructed,
158 constructed,
158− generated,
159+ canSign,
160+ generateAndSign,
159 signed,
161 signed,
162+ broadcast,
160 broadcasted,
163 broadcasted,
161 confirmed,
164 confirmed,
165+ reset,
166+ error,
162 setGasPrice,
167 setGasPrice,
163 },
168 },
164 };
169 };
src/lib/useInputs.js+3−5
@@ -151,7 +151,6 @@ const kPointValidators = [
151];
151];
152const kPointTransformers = [prependSig];
152const kPointTransformers = [prependSig];
153export function usePointInput(rest) {
153export function usePointInput(rest) {
154− const [lastValidPoint, setLastValidPoint] = useState('');
155 return useFirstOf(
154 return useFirstOf(
156 useForm([
155 useForm([
157 {
156 {
@@ -161,14 +160,13 @@ export function usePointInput(rest) {
161 validators: kPointValidators,
160 validators: kPointValidators,
162 transformers: kPointTransformers,
161 transformers: kPointTransformers,
163 mono: true,
162 mono: true,
164− onValue: setLastValidPoint,
165 ...rest,
163 ...rest,
166 },
164 },
167 ]),
165 ]),
168− ({ error, pass, focused }) => ({
166+ ({ error, pass, focused, data }) => ({
169− accessory: lastValidPoint ? (
167+ accessory: data ? (
170 <InputSigil
168 <InputSigil
171− patp={lastValidPoint}
169+ patp={data}
172 size={44}
170 size={44}
173 margin={8}
171 margin={8}
174 pass={pass}
172 pass={pass}
src/views/Activate/ActivateCode.js+3−5
@@ -36,7 +36,7 @@ export default function ActivateCode() {
36 setDerivedPoint,
36 setDerivedPoint,
37 } = useActivateFlow();
37 } = useActivateFlow();
38
38
39− const [ticketInput, { pass: validTicket }] = useTicketInput({
39+ const [ticketInput, { pass: validTicket, data: ticket }] = useTicketInput({
40 name: 'ticket',
40 name: 'ticket',
41 label: 'Code',
41 label: 'Code',
42 autoFocus: true,
42 autoFocus: true,
@@ -62,9 +62,7 @@ export default function ActivateCode() {
62 setDeriving(true);
62 setDeriving(true);
63 // when the ticket becomes valid, derive the point
63 // when the ticket becomes valid, derive the point
64 (async () => {
64 (async () => {
65− const { seed } = await generateTemporaryOwnershipWallet(
65+ const { seed } = await generateTemporaryOwnershipWallet(ticket);
66− ticketInput.data
67− );
68
66
69 //TODO isn't all this accessible in the ownership object?
67 //TODO isn't all this accessible in the ownership object?
70 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
68 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
@@ -112,7 +110,7 @@ export default function ActivateCode() {
112 }, [
110 }, [
113 validTicket,
111 validTicket,
114 contracts,
112 contracts,
115− ticketInput.data,
113+ ticket,
116 setDerivedPoint,
114 setDerivedPoint,
117 setDerivedWallet,
115 setDerivedWallet,
118 setInviteWallet,
116 setInviteWallet,
src/views/Admin/AdminSetProxy.js+75−37
@@ -1,25 +1,28 @@
1−import React, { useCallback } from 'react';
1+import React, { useCallback, useEffect, useState } from 'react';
2+import cn from 'classnames';
2−import { Grid, P, Text, Input, Flex, LinkButton } from 'indigo-react';
3+import { Grid, Text, Input, Flex, LinkButton } from 'indigo-react';
3import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
4
5
5import { useNetwork } from 'store/network';
6import { useNetwork } from 'store/network';
6−
7+import { usePointCache } from 'store/pointCache';
7−import * as need from 'lib/need';
8−import { useLocalRouter } from 'lib/LocalRouter';
9−import { ETH_ZERO_ADDR, eqAddr } from 'lib/wallet';
10−
11−import ViewHeader from 'components/ViewHeader';
12import { usePointCursor } from 'store/pointCursor';
8import { usePointCursor } from 'store/pointCursor';
9+
13import {
10import {
14 PROXY_TYPE,
11 PROXY_TYPE,
15 proxyTypeToHuman,
12 proxyTypeToHuman,
16 proxyTypeToHumanDescription,
13 proxyTypeToHumanDescription,
17} from 'lib/proxy';
14} from 'lib/proxy';
18−import MiniBackButton from 'components/MiniBackButton';
15+import * as need from 'lib/need';
16+import { useLocalRouter } from 'lib/LocalRouter';
17+import { ETH_ZERO_ADDR, eqAddr } from 'lib/wallet';
19import capitalize from 'lib/capitalize';
18import capitalize from 'lib/capitalize';
20−import { usePointCache } from 'store/pointCache';
21−import { GenerateButton } from 'components/Buttons';
22import { useAddressInput } from 'lib/useInputs';
19import { useAddressInput } from 'lib/useInputs';
20+import useEthereumTransaction from 'lib/useEthereumTransaction';
21+
22+import ViewHeader from 'components/ViewHeader';
23+import MiniBackButton from 'components/MiniBackButton';
24+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
25+import { GAS_LIMITS } from 'lib/constants';
23
26
24const proxyFromDetails = (details, proxyType) => {
27const proxyFromDetails = (details, proxyType) => {
25 switch (proxyType) {
28 switch (proxyType) {
@@ -36,16 +39,18 @@ const proxyFromDetails = (details, proxyType) => {
36 }
39 }
37};
40};
38
41
39−function useSetProxy(proxyType) {
42+function useSetProxy(proxyType, address) {
40− // TODO: abstract eth login into higher level useTransaction()
41 const { contracts } = useNetwork();
43 const { contracts } = useNetwork();
42 const { pointCursor } = usePointCursor();
44 const { pointCursor } = usePointCursor();
45+ const { syncOwnedPoint } = usePointCache();
46+
47+ const _contracts = need.contracts(contracts);
48+ const _point = need.point(pointCursor);
49+
50+ const [isUnsetting, setUnsetting] = useState(false);
43
51
44 const buildUnsignedTx = useCallback(
52 const buildUnsignedTx = useCallback(
45 address => {
53 address => {
46− const _contracts = need.contracts(contracts);
47− const _point = need.point(pointCursor);
48−
49 const txArgs = [_contracts, _point, address];
54 const txArgs = [_contracts, _point, address];
50
55
51 switch (proxyType) {
56 switch (proxyType) {
@@ -56,30 +61,55 @@ function useSetProxy(proxyType) {
56 case PROXY_TYPE.TRANSFER:
61 case PROXY_TYPE.TRANSFER:
57 return azimuth.ecliptic.setTransferProxy(...txArgs);
62 return azimuth.ecliptic.setTransferProxy(...txArgs);
58 case PROXY_TYPE.VOTING:
63 case PROXY_TYPE.VOTING:
59− return azimuth.ecliptic.setTransferProxy(...txArgs);
64+ return azimuth.ecliptic.setVotingProxy(...txArgs);
60 default:
65 default:
61 throw new Error(`Unknown proxyType ${proxyType}`);
66 throw new Error(`Unknown proxyType ${proxyType}`);
62 }
67 }
63 },
68 },
64− [proxyType, contracts, pointCursor]
69+ [_contracts, _point, proxyType]
65 );
70 );
66
71
72+ const {
73+ initializing,
74+ constructed,
75+ construct: _construct,
76+ constructAndSign,
77+ generateAndSign,
78+ sign,
79+ signed,
80+ broadcast,
81+ broadcasted,
82+ confirmed,
83+ reset,
84+ inputsLocked,
85+ bind,
86+ } = useEthereumTransaction(GAS_LIMITS.SET_PROXY);
87+
88+ // sync point details after success
89+ useEffect(() => {
90+ if (confirmed) {
91+ syncOwnedPoint(_point);
92+ }
93+ }, [_point, confirmed, syncOwnedPoint]);
94+
95+ // construct the unsigned transaction when we have a valid address
67− const generate = useCallback(
96+ const construct = useCallback(
68− async address => {
69− const utx = buildUnsignedTx(address);
97+ address => _construct(buildUnsignedTx(address)),
70− console.log(utx);
71− },
72− [buildUnsignedTx]
98+ [_construct, buildUnsignedTx]
73 );
99 );
74
100
101+ // force-unset
75− const unset = useCallback(() => generate(ETH_ZERO_ADDR), [generate]);
102+ const unset = useCallback(() => {
76−
103+ setUnsetting(true);
77− const locked = false;
104+ _construct(buildUnsignedTx(ETH_ZERO_ADDR));
105+ }, [_construct, buildUnsignedTx]);
78
106
79 return {
107 return {
80− locked,
108+ construct,
81− generate,
82 unset,
109 unset,
110+ isUnsetting,
111+ inputsLocked: inputsLocked || isUnsetting,
112+ bind,
83 };
113 };
84}
114}
85
115
@@ -92,14 +122,18 @@ export default function AdminSetProxy() {
92
122
93 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
123 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
94
124
95− const { generate, unset, locked } = useSetProxy(data.proxyType);
125+ const { construct, unset, inputsLocked, bind } = useSetProxy(data.proxyType);
96 const [addressInput, { pass, data: address }] = useAddressInput({
126 const [addressInput, { pass, data: address }] = useAddressInput({
97 name: 'address',
127 name: 'address',
98 label: `New ${properProxyType} Address`,
128 label: `New ${properProxyType} Address`,
99− disabled: locked,
129+ disabled: inputsLocked,
100 });
130 });
101
131
102− const _generate = useCallback(() => generate(address), [address, generate]);
132+ useEffect(() => {
133+ if (pass) {
134+ construct(address);
135+ }
136+ }, [pass, address, construct]);
103
137
104 const proxyAddress = proxyFromDetails(details, data.proxyType);
138 const proxyAddress = proxyFromDetails(details, data.proxyType);
105 const isProxySet = !eqAddr(ETH_ZERO_ADDR, proxyAddress);
139 const isProxySet = !eqAddr(ETH_ZERO_ADDR, proxyAddress);
@@ -117,11 +151,17 @@ export default function AdminSetProxy() {
117 Current {properProxyType} Address
151 Current {properProxyType} Address
118 </Grid.Item>
152 </Grid.Item>
119 <Grid.Item full as={Flex} row justify="between" align="center">
153 <Grid.Item full as={Flex} row justify="between" align="center">
154+ <Flex.Item
155+ flex
156+ as={Text}
120− <Flex.Item flex as={Text} className="mono">
157+ className={cn({
158+ 'mono black': isProxySet,
159+ gray4: !isProxySet,
160+ })}>
121 {isProxySet ? proxyAddress : 'Unset'}
161 {isProxySet ? proxyAddress : 'Unset'}
122 </Flex.Item>
162 </Flex.Item>
123 {isProxySet && (
163 {isProxySet && (
124− <Flex.Item as={LinkButton} onClick={unset}>
164+ <Flex.Item as={LinkButton} onClick={unset} disabled={inputsLocked}>
125 Unset
165 Unset
126 </Flex.Item>
166 </Flex.Item>
127 )}
167 )}
@@ -129,9 +169,7 @@ export default function AdminSetProxy() {
129
169
130 <Grid.Item full as={Input} {...addressInput} className="mv4" />
170 <Grid.Item full as={Input} {...addressInput} className="mv4" />
131
171
132− <Grid.Item full as={GenerateButton} onClick={_generate} disabled={!pass}>
172+ <Grid.Item full as={InlineEthereumTransaction} {...bind} />
133− Generate & Sign Transaction
134− </Grid.Item>
135 </Grid>
173 </Grid>
136 );
174 );
137}
175}
src/views/Admin/AdminTransfer.js+1−1
@@ -33,7 +33,7 @@ export default function AdminTransfer() {
33 <Grid.Item full as={ViewHeader}>
33 <Grid.Item full as={ViewHeader}>
34 Transfer Point
34 Transfer Point
35 </Grid.Item>
35 </Grid.Item>
36− <Grid.Item full as={P}>
36+ <Grid.Item full as={Text}>
37 Transfer {name} to a new owner.
37 Transfer {name} to a new owner.
38 </Grid.Item>
38 </Grid.Item>
39
39
src/views/Invite.js+4−1
@@ -19,6 +19,7 @@ import { LocalRouterProvider } from 'lib/LocalRouter';
19import InviteUrl from './Invite/InviteUrl';
19import InviteUrl from './Invite/InviteUrl';
20import InviteEmail from './Invite/InviteEmail';
20import InviteEmail from './Invite/InviteEmail';
21import ViewHeader from 'components/ViewHeader';
21import ViewHeader from 'components/ViewHeader';
22+import MiniBackButton from 'components/MiniBackButton';
22
23
23const NAMES = {
24const NAMES = {
24 URL: 'URL',
25 URL: 'URL',
@@ -57,6 +58,7 @@ export default function Invite() {
57 <LocalRouterProvider value={router}>
58 <LocalRouterProvider value={router}>
58 <Grid className="mb4">
59 <Grid className="mb4">
59 <Grid.Item
60 <Grid.Item
61+ full
60 as={Crumbs}
62 as={Crumbs}
61 routes={[
63 routes={[
62 {
64 {
@@ -67,8 +69,9 @@ export default function Invite() {
67 text: 'Invite',
69 text: 'Invite',
68 },
70 },
69 ]}
71 ]}
70− full
72+ className="mb4"
71 />
73 />
74+ <Grid.Item full as={MiniBackButton} onClick={() => history.pop()} />
72 <Grid.Item full as={ViewHeader}>
75 <Grid.Item full as={ViewHeader}>
73 Invite
76 Invite
74 </Grid.Item>
77 </Grid.Item>
src/views/Invite/InviteEmail.js+8−2
@@ -389,6 +389,14 @@ export default function InviteEmail() {
389 }
389 }
390 }, [isDone, syncInvites, point]);
390 }, [isDone, syncInvites, point]);
391
391
392+ useEffect(() => {
393+ for (const input of inputs) {
394+ if (input.pass) {
395+ syncHasReceivedForEmail(input.data);
396+ }
397+ }
398+ }, [inputs, syncHasReceivedForEmail]);
399+
392 return (
400 return (
393 <Grid gap={3}>
401 <Grid gap={3}>
394 <Grid.Item as={Grid} full>
402 <Grid.Item as={Grid} full>
@@ -439,8 +447,6 @@ export default function InviteEmail() {
439 as={Input}
447 as={Input}
440 cols={[1, 11]}
448 cols={[1, 11]}
441 {...input}
449 {...input}
442− onValue={email => syncHasReceivedForEmail(email)}
443− // NB(shrugs): ^ this feels like a hack?
444 accessory={accessoryFor(input.name)}
450 accessory={accessoryFor(input.name)}
445 />
451 />
446 {!isFirst &&
452 {!isFirst &&
src/views/Login.js+8−1
@@ -50,6 +50,8 @@ export default function Login() {
50 const { wallet } = useWallet();
50 const { wallet } = useWallet();
51 const { pointCursor, setPointCursor } = usePointCursor();
51 const { pointCursor, setPointCursor } = usePointCursor();
52
52
53+ const [deducing, setDeducing] = useState(false);
54+
53 // inputs
55 // inputs
54 const [currentTab, setCurrentTab] = useState(NAMES.TICKET);
56 const [currentTab, setCurrentTab] = useState(NAMES.TICKET);
55
57
@@ -71,6 +73,8 @@ export default function Login() {
71 const _wallet = need.wallet(wallet);
73 const _wallet = need.wallet(wallet);
72 const _contracts = need.contracts(contracts);
74 const _contracts = need.contracts(contracts);
73
75
76+ setDeducing(true);
77+
74 // if no point cursor set by login logic, try to deduce it
78 // if no point cursor set by login logic, try to deduce it
75 let deduced = pointCursor;
79 let deduced = pointCursor;
76 if (Nothing.hasInstance(deduced)) {
80 if (Nothing.hasInstance(deduced)) {
@@ -91,6 +95,8 @@ export default function Login() {
91 }
95 }
92 }
96 }
93
97
98+ setDeducing(false);
99+
94 // if we have a deduced point or one in the global context,
100 // if we have a deduced point or one in the global context,
95 // navigate to that specific point, otherwise navigate to list of points
101 // navigate to that specific point, otherwise navigate to list of points
96 if (Just.hasInstance(deduced)) {
102 if (Just.hasInstance(deduced)) {
@@ -124,7 +130,8 @@ export default function Login() {
124 as={ForwardButton}
130 as={ForwardButton}
125 solid
131 solid
126 className="mt2"
132 className="mt2"
133+ loading={deducing}
127− disabled={Nothing.hasInstance(wallet)}
134+ disabled={Nothing.hasInstance(wallet) || deducing}
128 onClick={doContinue}>
135 onClick={doContinue}>
129 Continue
136 Continue
130 </Grid.Item>
137 </Grid.Item>
src/views/Login/Keystore.js+1−1
@@ -23,7 +23,7 @@ export default function Keystore({ className }) {
23 // inputs
23 // inputs
24 // keystore: Maybe<String>
24 // keystore: Maybe<String>
25 const [keystore, setKeystore] = useState(Nothing());
25 const [keystore, setKeystore] = useState(Nothing());
26− const [passphraseInput, { data: passphrase }] = usePassphraseInput({
26+ const [passphraseInput, { value: passphrase }] = usePassphraseInput({
27 name: 'password',
27 name: 'password',
28 label: 'Keystore password',
28 label: 'Keystore password',
29 autoFocus: true,
29 autoFocus: true,
src/views/Login/Ticket.js+2−4
@@ -41,7 +41,7 @@ export default function Ticket({ className }) {
41 label: 'Wallet Passphrase',
41 label: 'Wallet Passphrase',
42 });
42 });
43
43
44− const [hasPassphraseInput] = useCheckboxInput({
44+ const [hasPassphraseInput, { data: hasPassphrase }] = useCheckboxInput({
45 name: 'has-passphrase',
45 name: 'has-passphrase',
46 label: 'Passphrase',
46 label: 'Passphrase',
47 initialValue: false,
47 initialValue: false,
@@ -171,9 +171,7 @@ export default function Ticket({ className }) {
171 <Grid.Item full as={Input} {...shard3Input} />
171 <Grid.Item full as={Input} {...shard3Input} />
172 </>
172 </>
173 )}
173 )}
174− {hasPassphraseInput.data && (
175− <Grid.Item full as={Input} {...passphraseInput} />
174+ {hasPassphrase && <Grid.Item full as={Input} {...passphraseInput} />}
176− )}
177
175
178 <Grid.Item full as={CheckboxInput} {...hasPassphraseInput} />
176 <Grid.Item full as={CheckboxInput} {...hasPassphraseInput} />
179 <Grid.Item full as={CheckboxInput} {...shardsInput} />
177 <Grid.Item full as={CheckboxInput} {...shardsInput} />