on master help sign in
checkpoint: initial code for InlineEthereumTransaction, useEthereumTransaction
commit06b6de476980b95020d8963f7cb7981074ebcfcc authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateTue 16 Jul 2019, 14:48 (7 years ago) parenta13b9eb
19 files changed+336−121
src/components/Buttons.js+4−1 src/components/InlineEthereumTransaction.js+94 src/indigo-react/components/CheckboxInput.js−2 src/indigo-react/components/Input.js+9−12 src/indigo-react/components/LinkButton.js+3−5 src/indigo-react/components/SelectInput.js−1 src/indigo-react/components/ToggleInput.js+68 src/indigo-react/index.js+1 src/indigo-react/lib/useForm.js+11−7 src/lib/useEthereumTransaction.js+41−36 src/lib/useInputs.js+3−5 src/views/Activate/ActivateCode.js+3−5 src/views/Admin/AdminSetProxy.js+75−37 src/views/Admin/AdminTransfer.js+1−1 src/views/Invite.js+4−1 src/views/Invite/InviteEmail.js+8−2 src/views/Login.js+8−1 src/views/Login/Keystore.js+1−1 src/views/Login/Ticket.js+2−4
src/components/Buttons.js+4−1
@@ -1,8 +1,11 @@
11import React from 'react';
22import { Button } from 'indigo-react';
3+import Blinky from './Blinky';
34
45// 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+);
69export const DownloadButton = props => <Button accessory="↓" {...props} />;
710export const RestartButton = props => <Button accessory="↺" solid {...props} />;
811export 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';
33
44import Flex from './Flex';
55
6−// TODO: make this look like screens
76export default function CheckboxInput({
87 // visuals
98 name,
@@ -11,7 +10,6 @@ export default function CheckboxInput({
1110 className,
1211
1312 // callbacks
14− onValue,
1513 onEnter,
1614
1715 // 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';
22import cn from 'classnames';
33
44import Flex from './Flex';
@@ -13,7 +13,6 @@ export default function Input({
1313 mono = false,
1414
1515 // callbacks
16− onValue,
1716 onEnter,
1817
1918 // state from hook
@@ -46,14 +45,6 @@ export default function Input({
4645 [disabled, pass] // eslint-disable-line react-hooks/exhaustive-deps
4746 );
4847
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−
5748 return (
5849 <Flex
5950 className={cn(className, 'mb1')}
@@ -64,7 +55,13 @@ export default function Input({
6455 cursor: 'not-allowed',
6556 }),
6657 }}>
67− <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
58+ <Flex.Item
59+ as="label"
60+ className={cn('f6 lh-tall', {
61+ black: !disabled,
62+ gray4: disabled,
63+ })}
64+ htmlFor={name}>
6865 {label}
6966 </Flex.Item>
7067 <Flex.Item as={Flex} className="rel" row>
@@ -86,7 +83,7 @@ export default function Input({
8683 'b-green3': visiblyPassed,
8784 'b-black': focused && !visiblyPassed,
8885 'b-yellow3': !focused && hintError,
89− 'b-gray3': !focused && !hintError && !visiblyPassed,
86+ 'b-gray2': !focused && !hintError && !visiblyPassed,
9087 }
9188 // TODO: inputClassName ?
9289 )}
src/indigo-react/components/LinkButton.js+3−5
@@ -3,7 +3,6 @@ import cn from 'classnames';
33
44export default function LinkButton({
55 as: As = 'a',
6− solid = false,
76 disabled = false,
87 className,
98 onClick,
@@ -20,11 +19,10 @@ export default function LinkButton({
2019 }),
2120 }}
2221 className={cn(
23− 'pointer ph2 underline',
22+ 'pointer underline',
2423 {
25− white: solid,
26− black: !solid && !disabled,
27− gray4: !solid && disabled,
24+ black: !disabled,
25+ gray4: disabled,
2826 },
2927 className
3028 )}
src/indigo-react/components/SelectInput.js−1
@@ -17,7 +17,6 @@ export default function SelectInput({
1717 placeholder,
1818
1919 // callbacks
20− onValue,
2120 onEnter,
2221
2322 // 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';
44export { default as Input } from './components/Input';
55export { default as SelectInput } from './components/SelectInput';
66export { default as CheckboxInput } from './components/CheckboxInput';
7+export { default as ToggleInput } from './components/ToggleInput';
78export { default as AccessoryIcon } from './components/AccessoryIcon';
89export { default as Button } from './components/Button';
910export { default as IconButton } from './components/IconButton';
src/indigo-react/lib/useForm.js+11−7
@@ -192,11 +192,16 @@ export default function useForm(inputConfigs = []) {
192192 configs.map(
193193 ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => {
194194 const value =
195− values[name] || (initialValue === undefined ? '' : initialValue);
195+ datas[i] !== undefined
196+ ? datas[i]
197+ : initialValue !== undefined
198+ ? initialValue
199+ : '';
196200 return {
197201 // Input props
198202 name,
199− data: datas[i],
203+ value: value,
204+ data: passes[i] ? datas[i] : undefined,
200205 pass: passes[i],
201206 visiblyPassed: visiblePasses[i],
202207 error: errors[i],
@@ -205,10 +210,10 @@ export default function useForm(inputConfigs = []) {
205210 autoFocus: autoFocus && !disabled,
206211 disabled,
207212 ...rest,
208− // dom properties below:
213+ // DOM Properties for <input />
209214 bind: {
210215 value,
211− checked: !!values[name],
216+ checked: !!datas[i],
212217 onChange: onChange(name),
213218 onFocus: onFocus(name),
214219 onBlur: onBlur(name),
@@ -220,15 +225,14 @@ export default function useForm(inputConfigs = []) {
220225 [
221226 configs,
222227 datas,
223− visiblePasses,
224228 passes,
229+ visiblePasses,
225230 errors,
226231 hintErrors,
227232 focuses,
228− values,
229− onBlur,
230233 onChange,
231234 onFocus,
235+ onBlur,
232236 ]
233237 );
234238
src/lib/useEthereumTransaction.js+41−36
@@ -15,7 +15,6 @@ import { toHex, toWei, fromWei } from 'web3-utils';
1515
1616const STATE = {
1717 NONE: 'NONE',
18− GENERATED: 'GENERATED',
1918 SIGNED: 'SIGNED',
2019 BROADCASTED: 'BROADCASTED',
2120 CONFIRMED: 'CONFIRMED',
@@ -23,13 +22,9 @@ const STATE = {
2322
2423/**
2524 * manage the state around sending and confirming an ethereum transaction
26− * @param {Maybe<EthereumTx>} unsignedTx
2725 * @param {number} initialGasLimit
2826 */
29−export default function useEthereumTransaction(
30− unsignedTx,
31− initialGasLimit = 0
32−) {
27+export default function useEthereumTransaction(initialGasLimit = 0) {
3328 const { wallet, walletType, walletHdPath } = useWallet();
3429 const { web3, networkType } = useNetwork();
3530 const _web3 = need.web3(web3);
@@ -45,48 +40,51 @@ export default function useEthereumTransaction(
4540 const [signedTransaction, setSignedTransaction] = useState();
4641
4742 const initializing = nonce === undefined || chainId === undefined;
48− const constructed = Just.hasInstance(unsignedTx);
49− const generated = state === STATE.GENERATED;
43+ const constructed = !!unsignedTransaction;
5044 const signed = state === STATE.SIGNED;
5145 const broadcasted = state === STATE.BROADCASTED;
5246 const confirmed = state === STATE.CONFIRMED;
5347
54− const generate = useCallback(() => {
55− const txn = unsignedTx.value;
56− try {
57− txn.gas = toHex(gasLimit);
58− txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
48+ const inputsLocked = signed || broadcasted || confirmed;
5949
60− setUnsignedTransaction(txn);
61− setState(STATE.GENERATED);
62− } catch (error) {
63− setError(error);
64− }
65− }, [gasLimit, gasPrice, unsignedTx.value]);
50+ const canSign = !initializing && constructed;
51+
52+ const construct = useCallback(txn => setUnsignedTransaction(txn), [
53+ setUnsignedTransaction,
54+ ]);
6655
67− const sign = useCallback(async () => {
56+ const generateAndSign = useCallback(async () => {
6857 try {
69− // TODO: sign the transaction
70− const txn = await signTransaction(
71− _wallet,
58+ // TODO: set varaibles
59+ // txn.gas = toHex(gasLimit);
60+ // txn.gasPrice = toHex(toWei(gasPrice, 'gwei'));
61+
62+ const txn = await signTransaction({
63+ wallet, // wallet: _wallet,
7264 walletType,
73− walletHdPath.getOrElse(''),
65+ walletHdPath,
7466 networkType,
67+ txn: Just(unsignedTransaction),
68+ setStx: () => {}, // TODO: remove
69+ nonce,
7570 chainId,
76− unsignedTransaction
77− );
78− const stx = hexify(txn.serialize());
71+ gasPrice,
72+ gasLimit,
73+ });
7974
80− setSignedTransaction(stx);
75+ setSignedTransaction(txn);
8176 setState(STATE.SIGNED);
8277 } catch (error) {
8378 setError(error);
8479 }
8580 }, [
86− _wallet,
8781 chainId,
82+ gasLimit,
83+ gasPrice,
8884 networkType,
85+ nonce,
8986 unsignedTransaction,
87+ wallet,
9088 walletHdPath,
9189 walletType,
9290 ]);
@@ -105,7 +103,11 @@ export default function useEthereumTransaction(
105103
106104 const broadcast = useCallback(async () => {
107105 try {
108− const txHash = await sendSignedTransaction(_web3, signedTransaction);
106+ const txHash = await sendSignedTransaction(
107+ _web3,
108+ Just(signedTransaction),
109+ /* doubtNonceError */ false
110+ );
109111
110112 setState(STATE.BROADCASTED);
111113 waitForConfirmation(txHash);
@@ -141,24 +143,27 @@ export default function useEthereumTransaction(
141143
142144 return {
143145 initializing,
146+ construct,
144147 constructed,
145− generate,
146− generated,
147− sign,
148+ canSign,
149+ generateAndSign,
148150 signed,
149151 broadcast,
150152 broadcasted,
151153 confirmed,
152154 reset,
153155 error,
156+ inputsLocked,
154157 bind: {
155− error,
156− initializing,
157158 constructed,
158− generated,
159+ canSign,
160+ generateAndSign,
159161 signed,
162+ broadcast,
160163 broadcasted,
161164 confirmed,
165+ reset,
166+ error,
162167 setGasPrice,
163168 },
164169 };
src/lib/useInputs.js+3−5
@@ -151,7 +151,6 @@ const kPointValidators = [
151151];
152152const kPointTransformers = [prependSig];
153153export function usePointInput(rest) {
154− const [lastValidPoint, setLastValidPoint] = useState('');
155154 return useFirstOf(
156155 useForm([
157156 {
@@ -161,14 +160,13 @@ export function usePointInput(rest) {
161160 validators: kPointValidators,
162161 transformers: kPointTransformers,
163162 mono: true,
164− onValue: setLastValidPoint,
165163 ...rest,
166164 },
167165 ]),
168− ({ error, pass, focused }) => ({
169− accessory: lastValidPoint ? (
166+ ({ error, pass, focused, data }) => ({
167+ accessory: data ? (
170168 <InputSigil
171− patp={lastValidPoint}
169+ patp={data}
172170 size={44}
173171 margin={8}
174172 pass={pass}
src/views/Activate/ActivateCode.js+3−5
@@ -36,7 +36,7 @@ export default function ActivateCode() {
3636 setDerivedPoint,
3737 } = useActivateFlow();
3838
39− const [ticketInput, { pass: validTicket }] = useTicketInput({
39+ const [ticketInput, { pass: validTicket, data: ticket }] = useTicketInput({
4040 name: 'ticket',
4141 label: 'Code',
4242 autoFocus: true,
@@ -62,9 +62,7 @@ export default function ActivateCode() {
6262 setDeriving(true);
6363 // when the ticket becomes valid, derive the point
6464 (async () => {
65− const { seed } = await generateTemporaryOwnershipWallet(
66− ticketInput.data
67− );
65+ const { seed } = await generateTemporaryOwnershipWallet(ticket);
6866
6967 //TODO isn't all this accessible in the ownership object?
7068 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
@@ -112,7 +110,7 @@ export default function ActivateCode() {
112110 }, [
113111 validTicket,
114112 contracts,
115− ticketInput.data,
113+ ticket,
116114 setDerivedPoint,
117115 setDerivedWallet,
118116 setInviteWallet,
src/views/Admin/AdminSetProxy.js+75−37
@@ -1,25 +1,28 @@
1−import React, { useCallback } from 'react';
2−import { Grid, P, Text, Input, Flex, LinkButton } from 'indigo-react';
1+import React, { useCallback, useEffect, useState } from 'react';
2+import cn from 'classnames';
3+import { Grid, Text, Input, Flex, LinkButton } from 'indigo-react';
34import * as azimuth from 'azimuth-js';
45
56import { useNetwork } from 'store/network';
6−
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';
7+import { usePointCache } from 'store/pointCache';
128import { usePointCursor } from 'store/pointCursor';
9+
1310import {
1411 PROXY_TYPE,
1512 proxyTypeToHuman,
1613 proxyTypeToHumanDescription,
1714} 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';
1918import capitalize from 'lib/capitalize';
20−import { usePointCache } from 'store/pointCache';
21−import { GenerateButton } from 'components/Buttons';
2219import { 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';
2326
2427const proxyFromDetails = (details, proxyType) => {
2528 switch (proxyType) {
@@ -36,16 +39,18 @@ const proxyFromDetails = (details, proxyType) => {
3639 }
3740};
3841
39−function useSetProxy(proxyType) {
40− // TODO: abstract eth login into higher level useTransaction()
42+function useSetProxy(proxyType, address) {
4143 const { contracts } = useNetwork();
4244 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);
4351
4452 const buildUnsignedTx = useCallback(
4553 address => {
46− const _contracts = need.contracts(contracts);
47− const _point = need.point(pointCursor);
48−
4954 const txArgs = [_contracts, _point, address];
5055
5156 switch (proxyType) {
@@ -56,30 +61,55 @@ function useSetProxy(proxyType) {
5661 case PROXY_TYPE.TRANSFER:
5762 return azimuth.ecliptic.setTransferProxy(...txArgs);
5863 case PROXY_TYPE.VOTING:
59− return azimuth.ecliptic.setTransferProxy(...txArgs);
64+ return azimuth.ecliptic.setVotingProxy(...txArgs);
6065 default:
6166 throw new Error(`Unknown proxyType ${proxyType}`);
6267 }
6368 },
64− [proxyType, contracts, pointCursor]
69+ [_contracts, _point, proxyType]
6570 );
6671
67− const generate = useCallback(
68− async address => {
69− const utx = buildUnsignedTx(address);
70− console.log(utx);
71− },
72− [buildUnsignedTx]
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
96+ const construct = useCallback(
97+ address => _construct(buildUnsignedTx(address)),
98+ [_construct, buildUnsignedTx]
7399 );
74100
75− const unset = useCallback(() => generate(ETH_ZERO_ADDR), [generate]);
76−
77− const locked = false;
101+ // force-unset
102+ const unset = useCallback(() => {
103+ setUnsetting(true);
104+ _construct(buildUnsignedTx(ETH_ZERO_ADDR));
105+ }, [_construct, buildUnsignedTx]);
78106
79107 return {
80− locked,
81− generate,
108+ construct,
82109 unset,
110+ isUnsetting,
111+ inputsLocked: inputsLocked || isUnsetting,
112+ bind,
83113 };
84114}
85115
@@ -92,14 +122,18 @@ export default function AdminSetProxy() {
92122
93123 const properProxyType = capitalize(proxyTypeToHuman(data.proxyType));
94124
95− const { generate, unset, locked } = useSetProxy(data.proxyType);
125+ const { construct, unset, inputsLocked, bind } = useSetProxy(data.proxyType);
96126 const [addressInput, { pass, data: address }] = useAddressInput({
97127 name: 'address',
98128 label: `New ${properProxyType} Address`,
99− disabled: locked,
129+ disabled: inputsLocked,
100130 });
101131
102− const _generate = useCallback(() => generate(address), [address, generate]);
132+ useEffect(() => {
133+ if (pass) {
134+ construct(address);
135+ }
136+ }, [pass, address, construct]);
103137
104138 const proxyAddress = proxyFromDetails(details, data.proxyType);
105139 const isProxySet = !eqAddr(ETH_ZERO_ADDR, proxyAddress);
@@ -117,11 +151,17 @@ export default function AdminSetProxy() {
117151 Current {properProxyType} Address
118152 </Grid.Item>
119153 <Grid.Item full as={Flex} row justify="between" align="center">
120− <Flex.Item flex as={Text} className="mono">
154+ <Flex.Item
155+ flex
156+ as={Text}
157+ className={cn({
158+ 'mono black': isProxySet,
159+ gray4: !isProxySet,
160+ })}>
121161 {isProxySet ? proxyAddress : 'Unset'}
122162 </Flex.Item>
123163 {isProxySet && (
124− <Flex.Item as={LinkButton} onClick={unset}>
164+ <Flex.Item as={LinkButton} onClick={unset} disabled={inputsLocked}>
125165 Unset
126166 </Flex.Item>
127167 )}
@@ -129,9 +169,7 @@ export default function AdminSetProxy() {
129169
130170 <Grid.Item full as={Input} {...addressInput} className="mv4" />
131171
132− <Grid.Item full as={GenerateButton} onClick={_generate} disabled={!pass}>
133− Generate & Sign Transaction
134− </Grid.Item>
172+ <Grid.Item full as={InlineEthereumTransaction} {...bind} />
135173 </Grid>
136174 );
137175}
src/views/Admin/AdminTransfer.js+1−1
@@ -33,7 +33,7 @@ export default function AdminTransfer() {
3333 <Grid.Item full as={ViewHeader}>
3434 Transfer Point
3535 </Grid.Item>
36− <Grid.Item full as={P}>
36+ <Grid.Item full as={Text}>
3737 Transfer {name} to a new owner.
3838 </Grid.Item>
3939
src/views/Invite.js+4−1
@@ -19,6 +19,7 @@ import { LocalRouterProvider } from 'lib/LocalRouter';
1919import InviteUrl from './Invite/InviteUrl';
2020import InviteEmail from './Invite/InviteEmail';
2121import ViewHeader from 'components/ViewHeader';
22+import MiniBackButton from 'components/MiniBackButton';
2223
2324const NAMES = {
2425 URL: 'URL',
@@ -57,6 +58,7 @@ export default function Invite() {
5758 <LocalRouterProvider value={router}>
5859 <Grid className="mb4">
5960 <Grid.Item
61+ full
6062 as={Crumbs}
6163 routes={[
6264 {
@@ -67,8 +69,9 @@ export default function Invite() {
6769 text: 'Invite',
6870 },
6971 ]}
70− full
72+ className="mb4"
7173 />
74+ <Grid.Item full as={MiniBackButton} onClick={() => history.pop()} />
7275 <Grid.Item full as={ViewHeader}>
7376 Invite
7477 </Grid.Item>
src/views/Invite/InviteEmail.js+8−2
@@ -389,6 +389,14 @@ export default function InviteEmail() {
389389 }
390390 }, [isDone, syncInvites, point]);
391391
392+ useEffect(() => {
393+ for (const input of inputs) {
394+ if (input.pass) {
395+ syncHasReceivedForEmail(input.data);
396+ }
397+ }
398+ }, [inputs, syncHasReceivedForEmail]);
399+
392400 return (
393401 <Grid gap={3}>
394402 <Grid.Item as={Grid} full>
@@ -439,8 +447,6 @@ export default function InviteEmail() {
439447 as={Input}
440448 cols={[1, 11]}
441449 {...input}
442− onValue={email => syncHasReceivedForEmail(email)}
443− // NB(shrugs): ^ this feels like a hack?
444450 accessory={accessoryFor(input.name)}
445451 />
446452 {!isFirst &&
src/views/Login.js+8−1
@@ -50,6 +50,8 @@ export default function Login() {
5050 const { wallet } = useWallet();
5151 const { pointCursor, setPointCursor } = usePointCursor();
5252
53+ const [deducing, setDeducing] = useState(false);
54+
5355 // inputs
5456 const [currentTab, setCurrentTab] = useState(NAMES.TICKET);
5557
@@ -71,6 +73,8 @@ export default function Login() {
7173 const _wallet = need.wallet(wallet);
7274 const _contracts = need.contracts(contracts);
7375
76+ setDeducing(true);
77+
7478 // if no point cursor set by login logic, try to deduce it
7579 let deduced = pointCursor;
7680 if (Nothing.hasInstance(deduced)) {
@@ -91,6 +95,8 @@ export default function Login() {
9195 }
9296 }
9397
98+ setDeducing(false);
99+
94100 // if we have a deduced point or one in the global context,
95101 // navigate to that specific point, otherwise navigate to list of points
96102 if (Just.hasInstance(deduced)) {
@@ -124,7 +130,8 @@ export default function Login() {
124130 as={ForwardButton}
125131 solid
126132 className="mt2"
127− disabled={Nothing.hasInstance(wallet)}
133+ loading={deducing}
134+ disabled={Nothing.hasInstance(wallet) || deducing}
128135 onClick={doContinue}>
129136 Continue
130137 </Grid.Item>
src/views/Login/Keystore.js+1−1
@@ -23,7 +23,7 @@ export default function Keystore({ className }) {
2323 // inputs
2424 // keystore: Maybe<String>
2525 const [keystore, setKeystore] = useState(Nothing());
26− const [passphraseInput, { data: passphrase }] = usePassphraseInput({
26+ const [passphraseInput, { value: passphrase }] = usePassphraseInput({
2727 name: 'password',
2828 label: 'Keystore password',
2929 autoFocus: true,
src/views/Login/Ticket.js+2−4
@@ -41,7 +41,7 @@ export default function Ticket({ className }) {
4141 label: 'Wallet Passphrase',
4242 });
4343
44− const [hasPassphraseInput] = useCheckboxInput({
44+ const [hasPassphraseInput, { data: hasPassphrase }] = useCheckboxInput({
4545 name: 'has-passphrase',
4646 label: 'Passphrase',
4747 initialValue: false,
@@ -171,9 +171,7 @@ export default function Ticket({ className }) {
171171 <Grid.Item full as={Input} {...shard3Input} />
172172 </>
173173 )}
174− {hasPassphraseInput.data && (
175− <Grid.Item full as={Input} {...passphraseInput} />
176− )}
174+ {hasPassphrase && <Grid.Item full as={Input} {...passphraseInput} />}
177175
178176 <Grid.Item full as={CheckboxInput} {...hasPassphraseInput} />
179177 <Grid.Item full as={CheckboxInput} {...shardsInput} />