on master help sign in
chore: form-ify inlinethtransction and netowrking keys
329f1a9Matt Condon (shrugs)
src/components/InlineEthereumTransaction.js+127−106
@@ -1,4 +1,4 @@
1−import React, { useEffect } from 'react';
1+import React, { useMemo, useCallback } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import {
3import {
4 Grid,
4 Grid,
@@ -12,12 +12,14 @@ import {
12} from 'indigo-react';
12} from 'indigo-react';
13import { fromWei } from 'web3-utils';
13import { fromWei } from 'web3-utils';
14
14
15−import { useCheckboxInput } from 'lib/useInputs';
16import { useExploreTxUrl } from 'lib/explorer';
15import { useExploreTxUrl } from 'lib/explorer';
17import { hexify } from 'lib/txn';
16import { hexify } from 'lib/txn';
18
17
19import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
18import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
20import WarningBox from './WarningBox';
19import WarningBox from './WarningBox';
20+import { composeValidator, buildCheckboxValidator } from 'form/Inputs';
21+import BridgeForm from 'form/BridgeForm';
22+import Condition from 'form/Condition';
21
23
22export default function InlineEthereumTransaction({
24export default function InlineEthereumTransaction({
23 // from useEthereumTransaction.bind
25 // from useEthereumTransaction.bind
@@ -57,29 +59,24 @@ export default function InlineEthereumTransaction({
57 const showSignedTx = signed;
59 const showSignedTx = signed;
58
60
59 const exploreTxUrl = useExploreTxUrl(txHash);
61 const exploreTxUrl = useExploreTxUrl(txHash);
60− const [advancedInput, { data: advancedOpen }] = useCheckboxInput({
61− name: 'advanced',
62− label: 'Advanced Configuration',
63− inverseLabel: 'Cancel Advanced Configuration',
64− disabled: !showConfigureInput,
65− });
66
62
67− const [
63+ const validate = useMemo(
68− viewSignedTransaction,
64+ () =>
69− { data: signedTransactionOpen },
65+ composeValidator({
70− ] = useCheckboxInput({
66+ useAdvanced: buildCheckboxValidator(),
71− name: 'viewsigned',
67+ viewSigned: buildCheckboxValidator(),
72− label: 'View Signed Transaction',
68+ }),
73− inverseLabel: 'Hide Signed Transaction',
69+ []
74− disabled: !showSignedTx,
75− });
70+ );
76
71
77− // reset gas price when closing advanced configuration
72+ const onValues = useCallback(
78− useEffect(() => {
73+ ({ valid, values, form }) => {
79− if (!advancedOpen) {
74+ if (!values.useAdvanced) {
80− resetGasPrice();
75+ resetGasPrice();
81− }
76+ }
77+ },
82− }, [advancedOpen, resetGasPrice]);
78+ [resetGasPrice]
79+ );
83
80
84 const renderPrimaryButton = () => {
81 const renderPrimaryButton = () => {
85 if (error) {
82 if (error) {
@@ -126,94 +123,118 @@ export default function InlineEthereumTransaction({
126
123
127 return (
124 return (
128 <Grid className={cn(className, 'mt1')}>
125 <Grid className={cn(className, 'mt1')}>
126+ <BridgeForm validate={validate} onValues={onValues} onSubmit={() => {}}>
127+ {({ handleSubmit }) => (
128+ <>
129− {renderPrimaryButton()}
129+ {renderPrimaryButton()}
130
130
131− {error && (
131+ {error && (
132− <Grid.Item full as={ErrorText} className="mt1">
132+ <Grid.Item full as={ErrorText} className="mt1">
133− {error.message}
133+ {error.message}
134− </Grid.Item>
135− )}
136−
137− {needFunds && (
138− <Grid.Item full as={WarningBox} className="mt3">
139− The address {needFunds.address} needs at least{' '}
140− {fromWei(needFunds.minBalance)} ETH and currently has{' '}
141− {fromWei(needFunds.balance)} ETH. Waiting until the account has enough
142− funds.
143− </Grid.Item>
144− )}
145−
146− {showConfigureInput && (
147− <>
148− <Grid.Item full as={ToggleInput} {...advancedInput} />
149− {advancedOpen && (
150− <>
151− <Grid.Item full as={Flex} row justify="between" className="mt2">
152− <Flex.Item as={H5}>Gas Price</Flex.Item>
153− <Flex.Item as={H5}>{gasPrice} Gwei</Flex.Item>
154− </Grid.Item>
155− {/* TODO(shrugs): move to indigo/RangeInput */}
156− <Grid.Item
157− full
158− as="input"
159− type="range"
160− min="1"
161− max="100"
162− value={gasPrice}
163− onChange={e => setGasPrice(parseInt(e.target.value, 10))}
164− />
165− <Grid.Item
166− full
167− as={Flex}
168− row
169− justify="between"
170− className="f6 mt1">
171− <Flex.Item as={Text}>Cheap</Flex.Item>
172− <Flex.Item as={Text}>Fast</Flex.Item>
173 </Grid.Item>
134 </Grid.Item>
135+ )}
136+
137+ {needFunds && (
174− <Grid.Item full as={HelpText} className="mt3">
138+ <Grid.Item full as={WarningBox} className="mt3">
175− Nonce: {nonce}
139+ The address {needFunds.address} needs at least{' '}
176− </Grid.Item>
140+ {fromWei(needFunds.minBalance)} ETH and currently has{' '}
177− <Grid.Item full as={HelpText} className="mt1">
141+ {fromWei(needFunds.balance)} ETH. Waiting until the account has
178− Chain ID: {chainId}
142+ enough funds.
179 </Grid.Item>
143 </Grid.Item>
180− <Grid.Divider className="mt3" />
181− </>
182− )}
183− </>
184− )}
144+ )}
185
145
186− {showSignedTx && (
146+ {showConfigureInput && (
187− <>
147+ <>
148+ <Grid.Item
149+ full
188− <Grid.Item full as={ToggleInput} {...viewSignedTransaction} />
150+ as={ToggleInput}
189− {signedTransactionOpen && (
151+ name="useAdvanced"
190− <Grid.Item full as="code" className="f6 mono gray4 wrap">
152+ label="Advanced Configuration"
191− {hexify(signedTransaction.serialize())}
153+ inverseLabel="Cancel Advanced Configuration"
192− </Grid.Item>
154+ disabled={!showConfigureInput}
193− )}
155+ />
194− </>
195− )}
196
156
157+ <Condition when="useAdvanced" is={true}>
158+ <Grid.Item
159+ full
160+ as={Flex}
161+ row
162+ justify="between"
163+ className="mt2">
164+ <Flex.Item as={H5}>Gas Price</Flex.Item>
165+ <Flex.Item as={H5}>{gasPrice} Gwei</Flex.Item>
166+ </Grid.Item>
167+ {/* TODO(shrugs): move to indigo/RangeInput */}
168+ <Grid.Item
169+ full
170+ as="input"
171+ type="range"
172+ min="1"
173+ max="100"
174+ value={gasPrice}
175+ onChange={e => setGasPrice(parseInt(e.target.value, 10))}
176+ />
177+ <Grid.Item
178+ full
179+ as={Flex}
180+ row
181+ justify="between"
182+ className="f6 mt1">
183+ <Flex.Item as={Text}>Cheap</Flex.Item>
184+ <Flex.Item as={Text}>Fast</Flex.Item>
185+ </Grid.Item>
186+ <Grid.Item full as={HelpText} className="mt3">
187+ Nonce: {nonce}
188+ </Grid.Item>
189+ <Grid.Item full as={HelpText} className="mt1">
190+ Chain ID: {chainId}
191+ </Grid.Item>
192+ <Grid.Divider className="mt3" />
193+ </Condition>
194+ </>
195+ )}
196+
197+ {showSignedTx && (
198+ <>
199+ <Grid.Item
200+ full
201+ as={ToggleInput}
202+ name="viewSigned"
203+ label="View Signed Transaction"
204+ inverseLabel="Hide Signed Transaction"
205+ disabled={!showSignedTx}
206+ />
207+ <Condition when="viewSigned" is={true}>
208+ <Grid.Item full as="code" className="f6 mono gray4 wrap">
209+ {hexify(signedTransaction.serialize())}
210+ </Grid.Item>
211+ </Condition>
212+ </>
213+ )}
214+
197− {showReceipt && (
215+ {showReceipt && (
198− <>
216+ <>
199− <Grid.Divider />
217+ <Grid.Divider />
200− <Grid.Item full as={Flex} col className="pv4">
218+ <Grid.Item full as={Flex} col className="pv4">
201− <Flex.Item as={Flex} row justify="between">
219+ <Flex.Item as={Flex} row justify="between">
202− <Flex.Item as={H5}>Transaction Hash</Flex.Item>
220+ <Flex.Item as={H5}>Transaction Hash</Flex.Item>
203− <Flex.Item as={LinkButton} href={exploreTxUrl}>
221+ <Flex.Item as={LinkButton} href={exploreTxUrl}>
204− Etherscan↗
222+ Etherscan↗
205− </Flex.Item>
223+ </Flex.Item>
206− </Flex.Item>
224+ </Flex.Item>
207− <Flex.Item as={Flex}>
225+ <Flex.Item as={Flex}>
208− <Flex.Item flex as="code" className="f6 mono gray4 wrap">
226+ <Flex.Item flex as="code" className="f6 mono gray4 wrap">
209− {txHash}
227+ {txHash}
210− </Flex.Item>
228+ </Flex.Item>
211− <Flex.Item flex />
229+ <Flex.Item flex />
212− </Flex.Item>
230+ </Flex.Item>
213− </Grid.Item>
231+ </Grid.Item>
214− <Grid.Divider />
232+ <Grid.Divider />
233+ </>
234+ )}
215− </>
235+ </>
216− )}
236+ )}
237+ </BridgeForm>
217 </Grid>
238 </Grid>
218 );
239 );
219}
240}
src/form/Inputs.js+2−2
@@ -94,7 +94,6 @@ export const composeValidator = (
94 };
94 };
95
95
96 return async values => {
96 return async values => {
97− console.log('validating...');
98 const errors = await fieldLevelValidator(values);
97 const errors = await fieldLevelValidator(values);
99 return await formValidator(values, errors);
98 return await formValidator(values, errors);
100 };
99 };
@@ -195,12 +194,13 @@ export function PointInput({ name, size = 4, ...rest }) {
195 );
194 );
196}
195}
197
196
198−export function PrivateKeyInput({ ...rest }) {
197+export function HexInput({ ...rest }) {
199 return (
198 return (
200 <Input
199 <Input
201 type="text"
200 type="text"
202 placeholder={PLACEHOLDER_PRIVATE_KEY}
201 placeholder={PLACEHOLDER_PRIVATE_KEY}
203 autoComplete="off"
202 autoComplete="off"
203+ mono
204 {...rest}
204 {...rest}
205 />
205 />
206 );
206 );
src/lib/useInputs.js−34
@@ -57,40 +57,6 @@ function useFirstOf({ inputs, setValue, ...rest }, mapper = identity) {
57 ];
57 ];
58}
58}
59
59
60−export function usePassphraseInput(props) {
61− return useFirstOf(
62− useForm([
63− {
64− type: 'password',
65− autoComplete: 'off',
66− placeholder: 'Passphrase',
67− ...props,
68− },
69− ])
70− );
71−}
72−
73−export function useHexInput({ length, ...rest }) {
74− return useFirstOf(
75− useForm([
76− {
77− type: 'text', // or password
78− autoComplete: 'off',
79− placeholder: PLACEHOLDER_PRIVATE_KEY,
80− validators: useMemo(
81− () => [
82− validateHexString,
83− validateLength(length + 2),
84− validateNotEmpty,
85− ],
86− [length]
87− ),
88− ...rest,
89− },
90− ])
91− );
92−}
93−
94const kMnemonicValidators = [validateMnemonic, validateNotEmpty];
60const kMnemonicValidators = [validateMnemonic, validateNotEmpty];
95export function useMnemonicInput(props) {
61export function useMnemonicInput(props) {
96 return useFirstOf(
62 return useFirstOf(
src/views/Admin/AdminNetworkingKeys.js+85−71
@@ -1,14 +1,6 @@
1−import React, { useCallback, useEffect, useRef, useState } from 'react';
1+import React, { useCallback, useMemo, useRef, useState } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3−import {
4− Grid,
5− Text,
6− H5,
7− Flex,
8− ToggleInput,
9− Input,
10− CheckboxInput,
11−} from 'indigo-react';
3+import { Grid, Text, H5, Flex, ToggleInput, CheckboxInput } from 'indigo-react';
12import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
13import { randomHex } from 'web3-utils';
5import { randomHex } from 'web3-utils';
14
6
@@ -30,7 +22,6 @@ import { formatDotsWithTime } from 'lib/dateFormat';
30import useEthereumTransaction from 'lib/useEthereumTransaction';
22import useEthereumTransaction from 'lib/useEthereumTransaction';
31import { GAS_LIMITS } from 'lib/constants';
23import { GAS_LIMITS } from 'lib/constants';
32import { addHexPrefix } from 'lib/wallet';
24import { addHexPrefix } from 'lib/wallet';
33−import { useHexInput, useCheckboxInput } from 'lib/useInputs';
34import useKeyfileGenerator from 'lib/useKeyfileGenerator';
25import useKeyfileGenerator from 'lib/useKeyfileGenerator';
35
26
36import ViewHeader from 'components/ViewHeader';
27import ViewHeader from 'components/ViewHeader';
@@ -40,6 +31,15 @@ import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
40import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
31import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
41import NoticeBox from 'components/NoticeBox';
32import NoticeBox from 'components/NoticeBox';
42
33
34+import {
35+ composeValidator,
36+ buildCheckboxValidator,
37+ buildHexValidator,
38+ HexInput,
39+} from 'form/Inputs';
40+import BridgeForm from 'form/BridgeForm';
41+import Condition from 'form/Condition';
42+
43const chainKeyProp = name => d =>
43const chainKeyProp = name => d =>
44 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
44 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
45
45
@@ -163,58 +163,44 @@ export default function AdminNetworkingKeys() {
163 keyfileBind,
163 keyfileBind,
164 } = useSetKeys();
164 } = useSetKeys();
165
165
166− const [showNetworkSeedInput, { data: showNetworkSeed }] = useCheckboxInput({
166+ const validateForm = useCallback((values, errors) => {
167− name: 'shownetworkseed',
167+ if (values.useNetworkSeed && errors.networkSeed) {
168− label: 'Use Custom Network Seed',
168+ return errors;
169− inverseLabel: 'Back to Derived Network Seed',
169+ }
170− initialValue: false,
171− disabled: inputsLocked,
172− });
173
170
171+ return {};
172+ }, []);
173+
174− const [
174+ const validate = useMemo(
175− networkSeedInput,
175+ () =>
176− { pass: validNetworkSeed, data: networkSeed },
176+ composeValidator(
177− { reset: resetNetworkSeed },
177+ {
178− ] = useHexInput({
178+ useNetworkSeed: buildCheckboxValidator(),
179− name: 'networkseed',
179+ networkSeed: buildHexValidator(32),
180− label: 'Network Seed (64 bytes)',
180+ useDiscontinuity: buildCheckboxValidator(),
181− length: 32, // 64 bytes
181+ },
182− disabled: inputsLocked,
182+ validateForm
183+ ),
184+ [validateForm]
183− });
185+ );
184
186
185− const [
187+ const onValues = useCallback(
186− discontinuityInput,
188+ ({ valid, values, form }) => {
187− { pass: validDiscontinuity, data: isDiscontinuity },
189+ if (valid) {
188− ] = useCheckboxInput({
190+ construct(
189− name: 'discontinuity',
191+ values.useNetworkSeed ? values.networkSeed : undefined,
190− label: 'Trigger New Continuity Era',
192+ values.useDiscontinuity
191− initialValue: false,
193+ );
192− disabled: inputsLocked,
194+ } else {
193− });
195+ unconstruct();
196+ }
194
197
195− useEffect(() => {
198+ if (!values.useNetworkSeed && values.networkSeed) {
196− const nothingOrValidSeed =
199+ form.change('networkSeed', '');
197− !showNetworkSeed || (showNetworkSeed && validNetworkSeed);
200+ }
198− if (nothingOrValidSeed && validDiscontinuity) {
199− construct(networkSeed, isDiscontinuity);
200− } else {
201− unconstruct();
202− }
201+ },
203− }, [
204− construct,
205− unconstruct,
202+ [construct, unconstruct]
206− isDiscontinuity,
203+ );
207− networkSeed,
208− showNetworkSeed,
209− validDiscontinuity,
210− validNetworkSeed,
211− ]);
212−
213− useEffect(() => {
214− if (!showNetworkSeed) {
215− resetNetworkSeed();
216− }
217− }, [resetNetworkSeed, showNetworkSeed]);
218
204
219 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
205 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
220
206
@@ -335,20 +321,48 @@ export default function AdminNetworkingKeys() {
335 )}
321 )}
336
322
337 {!completed && (
323 {!completed && (
338− <>
324+ <BridgeForm
339− <Grid.Item full as={ToggleInput} {...showNetworkSeedInput} />
325+ validate={validate}
340− {showNetworkSeed && (
326+ onValues={onValues}
327+ onSubmit={() => {}}
328+ initialValues={{
329+ useNetworkSeed: false,
330+ useDiscontinuity: false,
331+ }}>
332+ {({ handleSubmit }) => (
341 <>
333 <>
334+ <Grid.Item
335+ full
336+ as={ToggleInput}
337+ name="useNetworkSeed"
338+ label="Use Custom Network Seed"
339+ inverseLabel="Back to Derived Network Seed"
340+ disabled={inputsLocked}
341+ />
342+ <Condition when="useNetworkSeed" is={true}>
342− <Grid.Item full as={NoticeBox} className="mb2">
343+ <Grid.Item full as={NoticeBox} className="mb2">
343− When using a custom network seed, you'll need to download your
344+ When using a custom network seed, you'll need to download
344− Arvo keyfile immediately after this transaction is
345+ your Arvo keyfile immediately after this transaction is
345− completed—Multipass does not store your seed.
346+ completed—Multipass does not store your seed.
346− </Grid.Item>
347+ </Grid.Item>
347− <Grid.Item full as={Input} {...networkSeedInput} />
348+ <Grid.Item
349+ full
350+ as={HexInput}
351+ name="networkSeed"
352+ label="Network Seed (64 bytes)"
353+ disabled={inputsLocked}
354+ />
355+ </Condition>
356+ <Grid.Item
357+ full
358+ as={CheckboxInput}
359+ name="useDiscontinuity"
360+ label="Trigger New Continuity Era"
361+ disabled={inputsLocked}
362+ />
348 </>
363 </>
349 )}
364 )}
350− <Grid.Item full as={CheckboxInput} {...discontinuityInput} />
365+ </BridgeForm>
351− </>
352 )}
366 )}
353
367
354 <Grid.Item
368 <Grid.Item
src/views/Login/PrivateKey.js+2−6
@@ -9,11 +9,7 @@ import useLoginView from 'lib/useLoginView';
9import FormError from 'form/FormError';
9import FormError from 'form/FormError';
10import ContinueButton from './ContinueButton';
10import ContinueButton from './ContinueButton';
11import BridgeForm from 'form/BridgeForm';
11import BridgeForm from 'form/BridgeForm';
12−import {
13− buildHexValidator,
12+import { buildHexValidator, composeValidator, HexInput } from 'form/Inputs';
14− composeValidator,
15− PrivateKeyInput,
16−} from 'form/Inputs';
17
13
18export default function PrivateKey({ className, goHome }) {
14export default function PrivateKey({ className, goHome }) {
19 useLoginView(WALLET_TYPES.PRIVATE_KEY);
15 useLoginView(WALLET_TYPES.PRIVATE_KEY);
@@ -48,7 +44,7 @@ export default function PrivateKey({ className, goHome }) {
48 <>
44 <>
49 <Grid.Item
45 <Grid.Item
50 full
46 full
51− as={PrivateKeyInput}
47+ as={HexInput}
52 name="privatekey"
48 name="privatekey"
53 label="Private key"
49 label="Private key"
54 />
50 />