on master help sign in
feat: finalize final-form migration
commit6526dd7a5b44018abbeff40aeb54e18095c38fc9 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateTue 6 Aug 2019, 10:46 (7 years ago) parent21d1c95
25 files changed+418−339
src/components/InlineEthereumTransaction.js+3−3 src/components/UploadButton.js+3−1 src/form/BridgeForm.js+33−3 src/form/Inputs.js−99 src/form/validators.js+104 src/indigo-react/components/SelectInput.js−1 src/lib/useEthereumTransaction.js+12−4 src/lib/useRouter.js+2−2 src/views/Activate/ActivateCode.js+24−41 src/views/Activate/PassportVerify.js+10−10 src/views/Admin/AdminNetworkingKeys.js+21−17 src/views/Admin/AdminSetProxy.js+14−13 src/views/Admin/AdminTransfer.js+3−6 src/views/Admin/Reticket/ReticketVerify.js+11−9 src/views/CreateGalaxy.js+3−3 src/views/Disclaimer.js+7−2 src/views/Invite/InviteEmail.js+88−70 src/views/IssueChild.js+3−4 src/views/Login/Keystore.js+4−4 src/views/Login/Ledger.js+17−12 src/views/Login/Mnemonic.js+9−6 src/views/Login/PrivateKey.js+6−3 src/views/Login/Ticket.js+16−11 src/views/Login/Trezor.js+17−8 src/views/Party/PartySetPoolSize.js+8−7
src/components/InlineEthereumTransaction.js+3−3
@@ -17,7 +17,7 @@ import { hexify } from 'lib/txn';
1717
1818import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
1919import WarningBox from './WarningBox';
20−import { composeValidator, buildCheckboxValidator } from 'form/Inputs';
20+import { composeValidator, buildCheckboxValidator } from 'form/validators';
2121import BridgeForm from 'form/BridgeForm';
2222import Condition from 'form/Condition';
2323
@@ -123,7 +123,7 @@ export default function InlineEthereumTransaction({
123123
124124 return (
125125 <Grid className={cn(className, 'mt1')}>
126− <BridgeForm validate={validate} onValues={onValues} onSubmit={() => {}}>
126+ <BridgeForm validate={validate} onValues={onValues}>
127127 {({ handleSubmit }) => (
128128 <>
129129 {renderPrimaryButton()}
@@ -151,7 +151,7 @@ export default function InlineEthereumTransaction({
151151 name="useAdvanced"
152152 label="Advanced Configuration"
153153 inverseLabel="Cancel Advanced Configuration"
154− disabled={!showConfigureInput}
154+ disabled={!showConfigureInput || initializing}
155155 />
156156
157157 <Condition when="useAdvanced" is={true}>
src/components/UploadButton.js+3−1
@@ -6,7 +6,9 @@ export default function UploadButton({ children, onChange, ...rest }) {
66
77 return (
88 <label htmlFor="file" {...rest}>
9− <Button accessory="↑">{children}</Button>
9+ <Button accessory="↑" solid>
10+ {children}
11+ </Button>
1012 <input
1113 id="file"
1214 ref={button}
src/form/BridgeForm.js+33−3
@@ -1,14 +1,44 @@
1−import React from 'react';
1+import React, { useCallback } from 'react';
22import { Form } from 'react-final-form';
33import setFieldData from 'final-form-set-field-data';
44import arrayMutators from 'final-form-arrays';
5+import { noop } from 'lodash';
56
67import ValuesHandler from './ValuesHandler';
78import ValidationPauser from './ValidationPauser';
89
9−export default function BridgeForm({ children, onValues, ...rest }) {
10+/**
11+ * BridgeForm adds nice-to-haves for every form in bridge.
12+ * + afterSubmit callback for redirects
13+ * + default onSubmit to noop so react-final-form doesn't throw
14+ * + pauses validation when the form is unmounted
15+ * + `onValues` handler for reactive changes
16+ * + automatically includes array and field data mutators
17+ */
18+export default function BridgeForm({
19+ children,
20+ onValues,
21+ onSubmit = noop,
22+ afterSubmit = noop,
23+ ...rest
24+}) {
25+ const _onSubmit = useCallback(
26+ async (...args) => {
27+ const errors = await onSubmit(...args);
28+ if (errors) {
29+ return errors;
30+ }
31+
32+ afterSubmit();
33+ },
34+ [afterSubmit, onSubmit]
35+ );
36+
1037 return (
11− <Form mutators={{ setFieldData, ...arrayMutators }} {...rest}>
38+ <Form
39+ mutators={{ setFieldData, ...arrayMutators }}
40+ onSubmit={_onSubmit}
41+ {...rest}>
1242 {formProps => (
1343 <>
1444 <ValidationPauser />
src/form/Inputs.js−99
@@ -1,23 +1,7 @@
11import React from 'react';
22import { Input, AccessoryIcon } from 'indigo-react';
33import { useField } from 'react-final-form';
4−import { some } from 'lodash';
54
6−import {
7− validateNotEmpty,
8− validateTicket,
9− kDefaultValidator,
10− validateMnemonic,
11− validatePoint,
12− validateMaximumPatpByteLength,
13− validateOneOf,
14− validateHexString,
15− validateHexLength,
16− validateEthereumAddress,
17− validateGreaterThan,
18− validateEmail,
19−} from 'lib/validators';
20−import { compose } from 'lib/lib';
215import { prependSig, convertToNumber } from 'lib/transformers';
226import { DEFAULT_HD_PATH } from 'lib/wallet';
237import InputSigil from 'components/InputSigil';
@@ -32,89 +16,6 @@ const PLACEHOLDER_PRIVATE_KEY =
3216 '0x12345abcdee6beb2f323fab48b432925c9785808d33a6ca6d7ba00b45e9370c3';
3317const PLACEHOLDER_EMAIL = 'Email Address';
3418
35−const buildValidator = (
36− validators = [],
37− fn = () => undefined
38−) => async value => {
39− return (
40− compose(
41− ...validators,
42− kDefaultValidator
43− )(value).error || (await fn(value))
44− );
45−};
46−
47−export const hasErrors = obj => some(obj, v => v !== undefined);
48−
49−export const buildTicketValidator = (validators = []) =>
50− buildValidator([...validators, validateTicket, validateNotEmpty]);
51−export const buildMnemonicValidator = () =>
52− buildValidator([validateMnemonic, validateNotEmpty]);
53−export const buildCheckboxValidator = mustBe =>
54− buildValidator([
55− validateOneOf(mustBe !== undefined ? [mustBe] : [true, false]),
56− ]);
57−export const buildPassphraseValidator = () => buildValidator([]);
58−// TODO: validate hdpath format
59−export const buildHdPathValidator = () => buildValidator([validateNotEmpty]);
60−export const buildPointValidator = (size = 4, validate) =>
61− buildValidator(
62− [validatePoint, validateMaximumPatpByteLength(size), validateNotEmpty],
63− validate
64− );
65−export const buildSelectValidator = options =>
66− buildValidator([validateOneOf(options.map(option => option.value))]);
67−export const buildHexValidator = length =>
68− buildValidator([
69− validateHexLength(length),
70− validateHexString,
71− validateNotEmpty,
72− ]);
73−export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
74−export const buildAddressValidator = () =>
75− buildValidator([
76− validateEthereumAddress,
77− validateHexLength(40),
78− validateHexString,
79− validateNotEmpty,
80− ]);
81−export const buildNumberValidator = (min = 0) =>
82− buildValidator([validateGreaterThan(min)]);
83−export const buildEmailValidator = () =>
84− buildValidator([validateEmail, validateNotEmpty]);
85−
86−// the default form validator just returns field-level validations
87−const kDefaultFormValidator = (values, errors) => errors;
88−export const composeValidator = (
89− fieldValidators = {},
90− formValidator = kDefaultFormValidator
91−) => {
92− const names = Object.keys(fieldValidators);
93−
94− const fieldLevelValidators = names.map(name => value =>
95− fieldValidators[name](value)
96− );
97−
98− const fieldLevelValidator = async values => {
99− const errors = await Promise.all(
100− names.map((name, i) => fieldLevelValidators[i](values[name]))
101− );
102−
103− return names.reduce(
104− (memo, name, i) => ({
105− ...memo,
106− [name]: errors[i],
107− }),
108− {}
109− );
110− };
111−
112− return async values => {
113− const errors = await fieldLevelValidator(values);
114− return await formValidator(values, errors);
115− };
116−};
117−
11819export function TicketInput({ name, ...rest }) {
11920 const {
12021 meta: { valid, error, validating, touched, active },
src/form/validators.jsnew file+104
@@ -0,0 +1,104 @@
1+import { some } from 'lodash';
2+
3+import {
4+ validateNotEmpty,
5+ validateTicket,
6+ kDefaultValidator,
7+ validateMnemonic,
8+ validatePoint,
9+ validateMaximumPatpByteLength,
10+ validateOneOf,
11+ validateHexString,
12+ validateHexLength,
13+ validateEthereumAddress,
14+ validateGreaterThan,
15+ validateEmail,
16+} from 'lib/validators';
17+import { compose } from 'lib/lib';
18+
19+const buildValidator = (
20+ validators = [],
21+ fn = () => undefined
22+) => async value => {
23+ return (
24+ compose(
25+ ...validators,
26+ kDefaultValidator
27+ )(value).error || (await fn(value))
28+ );
29+};
30+
31+// error object has errors if some of its fields are
32+// 1) an array with any defined values
33+// 2) any defined values
34+export const hasErrors = iter =>
35+ some(iter, v => (Array.isArray(v) ? hasErrors(v) : v !== undefined));
36+
37+export const buildTicketValidator = (validators = []) =>
38+ buildValidator([...validators, validateTicket, validateNotEmpty]);
39+export const buildMnemonicValidator = () =>
40+ buildValidator([validateMnemonic, validateNotEmpty]);
41+export const buildCheckboxValidator = mustBe =>
42+ buildValidator([
43+ validateOneOf(mustBe !== undefined ? [mustBe] : [true, false]),
44+ ]);
45+export const buildPassphraseValidator = () => buildValidator([]);
46+// TODO: validate hdpath format
47+export const buildHdPathValidator = () => buildValidator([validateNotEmpty]);
48+export const buildPointValidator = (size = 4, validate) =>
49+ buildValidator(
50+ [validatePoint, validateMaximumPatpByteLength(size), validateNotEmpty],
51+ validate
52+ );
53+export const buildSelectValidator = options =>
54+ buildValidator([validateOneOf(options.map(option => option.value))]);
55+export const buildHexValidator = length =>
56+ buildValidator([
57+ validateHexLength(length),
58+ validateHexString,
59+ validateNotEmpty,
60+ ]);
61+export const buildUploadValidator = () => buildValidator([validateNotEmpty]);
62+export const buildAddressValidator = () =>
63+ buildValidator([
64+ validateEthereumAddress,
65+ validateHexLength(40),
66+ validateHexString,
67+ validateNotEmpty,
68+ ]);
69+export const buildNumberValidator = (min = 0) =>
70+ buildValidator([validateGreaterThan(min)]);
71+export const buildEmailValidator = () =>
72+ buildValidator([validateEmail, validateNotEmpty]);
73+
74+// the default form validator just returns field-level validations
75+const kDefaultFormValidator = (values, errors) => errors;
76+export const composeValidator = (
77+ fieldValidators = {},
78+ formValidator = kDefaultFormValidator
79+) => {
80+ const names = Object.keys(fieldValidators);
81+
82+ const fieldLevelValidators = names.map(name => value =>
83+ fieldValidators[name](value)
84+ );
85+
86+ const fieldLevelValidator = async values => {
87+ const errors = await Promise.all(
88+ names.map((name, i) => fieldLevelValidators[i](values[name]))
89+ );
90+
91+ return names.reduce(
92+ (memo, name, i) => ({
93+ ...memo,
94+ [name]: errors[i],
95+ }),
96+ {}
97+ );
98+ };
99+
100+ return async values => {
101+ const errors = await fieldLevelValidator(values);
102+ return await formValidator(values, errors);
103+ };
104+};
src/indigo-react/components/SelectInput.js−1
@@ -44,7 +44,6 @@ export default function SelectInput({
4444 setIsOpen(false);
4545 };
4646
47− console.log(input);
4847 const text = options.find(o => o.value === input.value).text;
4948
5049 return (
src/lib/useEthereumTransaction.js+12−4
@@ -189,6 +189,7 @@ export default function useEthereumTransaction(
189189 ]);
190190
191191 useEffect(() => {
192+ let mounted = true;
192193 // if nonce or chainId is undefined, re-fetch on-chain info
193194 if (!(nonce === undefined || chainId === undefined)) {
194195 return;
@@ -203,19 +204,26 @@ export default function useEthereumTransaction(
203204 _web3.eth.getGasPrice(),
204205 ]);
205206
207+ const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
208+
209+ if (!mounted) {
210+ return;
211+ }
212+
206213 setNonce(nonce);
207214 setChainId(chainId);
208− const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
209215 setSuggestedGasPrice(gasPrice);
210216 setGasPrice(gasPrice);
211217 } catch (error) {
212218 setError(error);
213219 }
214220 })();
221+
222+ return () => (mounted = false);
215223 }, [_wallet, _web3, setError, nonce, chainId]);
216224
217225 useEffect(() => {
218− let cancelled = false;
226+ let mounted = true;
219227
220228 if (confirmed) {
221229 (async () => {
@@ -234,7 +242,7 @@ export default function useEthereumTransaction(
234242 }
235243 }
236244
237− if (cancelled) {
245+ if (!mounted) {
238246 return;
239247 }
240248
@@ -242,7 +250,7 @@ export default function useEthereumTransaction(
242250 })();
243251 }
244252
245− return () => (cancelled = true);
253+ return () => (mounted = false);
246254 }, [confirmed, refetch, completed, setError]);
247255
248256 const values = useDeepEqualReference({
src/lib/useRouter.js+2−2
@@ -53,7 +53,7 @@ export default function useRouter({
5353 if (size <= 1) {
5454 // if we are at the root, pass this event to our parent
5555 if (oldPopState.current) {
56− window.history.back();
56+ oldPopState.current();
5757 }
5858 return;
5959 }
@@ -108,7 +108,7 @@ export default function useRouter({
108108
109109 // construct new onpopstate handler
110110 window.onpopstate = e => {
111− e.stopImmediatePropagation();
111+ e && e.stopImmediatePropagation();
112112 pop();
113113 };
114114
src/views/Activate/ActivateCode.js+24−41
@@ -1,5 +1,5 @@
1−import React, { useCallback, useMemo, useRef } from 'react';
2−import { Just, Nothing } from 'folktale/maybe';
1+import React, { useCallback, useMemo } from 'react';
2+import { Just } from 'folktale/maybe';
33import * as azimuth from 'azimuth-js';
44import { Grid, H4 } from 'indigo-react';
55
@@ -24,12 +24,8 @@ import useHasDisclaimed from 'lib/useHasDisclaimed';
2424
2525import BridgeForm from 'form/BridgeForm';
2626import SubmitButton from 'form/SubmitButton';
27−import {
28− TicketInput,
29− hasErrors,
30− composeValidator,
31− buildTicketValidator,
32−} from 'form/Inputs';
27+import { TicketInput } from 'form/Inputs';
28+import { composeValidator, buildTicketValidator } from 'form/validators';
3329import FormError from 'form/FormError';
3430import { FORM_ERROR } from 'final-form';
3531
@@ -40,9 +36,6 @@ export default function ActivateCode() {
4036 const impliedTicket = useImpliedTicket();
4137 const [hasDisclaimed] = useHasDisclaimed();
4238
43− const cachedInviteWallet = useRef(Nothing());
44− const cachedPoint = useRef();
45−
4639 const {
4740 setDerivedWallet,
4841 setInviteWallet,
@@ -53,6 +46,7 @@ export default function ActivateCode() {
5346 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
5447 history,
5548 ]);
49+
5650 const goToPassport = useCallback(() => {
5751 push(names.PASSPORT);
5852
@@ -61,23 +55,22 @@ export default function ActivateCode() {
6155 }
6256 }, [names, push, hasDisclaimed]);
6357
64− // validate should be a pure function but we don't want to have to recompute
65− // all of this information on submit, so cache the invite wallet and avoid
66− // re-renders that may trigger re-validations (causing infinite loop)
67− const validateForm = useCallback(
68− async ({ ticket }, errors) => {
69− if (hasErrors(errors)) {
70− return errors;
71− }
58+ const validate = useMemo(
59+ () => composeValidator({ ticket: buildTicketValidator() }),
60+ []
61+ );
7262
63+ // set our state on submission
64+ const onSubmit = useCallback(
65+ async values => {
7366 await timeout(100); // allow the ui changes to flush before we lag it out
7467
7568 const _contracts = need.contracts(contracts);
76− const { seed } = await generateTemporaryOwnershipWallet(ticket);
69+ const { seed } = await generateTemporaryOwnershipWallet(values.ticket);
7770
78− cachedInviteWallet.current = walletFromMnemonic(seed, DEFAULT_HD_PATH);
71+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
7972
80− const _inviteWallet = need.wallet(cachedInviteWallet.current);
73+ const _inviteWallet = need.wallet(inviteWallet);
8174
8275 const owned = await azimuth.azimuth.getOwnedPoints(
8376 _contracts,
@@ -99,8 +92,10 @@ export default function ActivateCode() {
9992 }
10093
10194 const point = parseInt(incoming[0], 10);
95+
10296 setDerivedPoint(Just(point));
103− cachedPoint.current = point;
97+ setInviteWallet(inviteWallet);
98+ setDerivedWallet(Just(await generateWallet(point)));
10499 } else {
105100 return {
106101 [FORM_ERROR]:
@@ -109,24 +104,12 @@ export default function ActivateCode() {
109104 };
110105 }
111106 },
112− [contracts, setDerivedPoint]
107+ [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
113108 );
114109
115− const validate = useMemo(
116− () => composeValidator({ ticket: buildTicketValidator() }, validateForm),
117− [validateForm]
118− );
119−
120− // set our state on submission
121− const onSubmit = useCallback(
122− async values => {
123− setInviteWallet(cachedInviteWallet.current);
124− setDerivedWallet(Just(await generateWallet(cachedPoint)));
125− },
126− [setDerivedWallet, setInviteWallet]
127− );
128−
129− const afterSubmit = useCallback(async () => goToPassport(), [goToPassport]);
110+ const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
111+ impliedTicket,
112+ ]);
130113
131114 return (
132115 <View inset>
@@ -138,8 +121,8 @@ export default function ActivateCode() {
138121 <BridgeForm
139122 validate={validate}
140123 onSubmit={onSubmit}
141− afterSubmit={afterSubmit}
142− initialValues={{ ticket: impliedTicket || '' }}>
124+ afterSubmit={goToPassport}
125+ initialValues={initialValues}>
143126 {({ validating, submitting, handleSubmit }) => (
144127 <>
145128 <Grid.Item
src/views/Activate/PassportVerify.js+10−10
@@ -7,11 +7,8 @@ import { validateExactly } from 'lib/validators';
77import { isDevelopment } from 'lib/flags';
88
99import SubmitButton from 'form/SubmitButton';
10−import {
11− TicketInput,
12− composeValidator,
13− buildTicketValidator,
14−} from 'form/Inputs';
10+import { TicketInput } from 'form/Inputs';
11+import { composeValidator, buildTicketValidator } from 'form/validators';
1512import BridgeForm from 'form/BridgeForm';
1613
1714import { useActivateFlow } from './ActivateFlow';
@@ -36,7 +33,12 @@ export default function PassportVerify({ className }) {
3633 [ticket]
3734 );
3835
39− const onSubmit = useCallback(() => goToTransfer(), [goToTransfer]);
36+ const initialValues = useMemo(
37+ () => ({
38+ ticket: STUB_VERIFY_TICKET ? ticket : undefined,
39+ }),
40+ [ticket]
41+ );
4042
4143 return (
4244 <PassportView header="Verify Passport" step={2} className={className}>
@@ -48,10 +50,8 @@ export default function PassportVerify({ className }) {
4850 </Grid.Item>
4951 <BridgeForm
5052 validate={validate}
51− onSubmit={onSubmit}
52− initialValues={{
53− ticket: STUB_VERIFY_TICKET ? ticket : undefined,
54− }}>
53+ afterSubmit={goToTransfer}
54+ initialValues={initialValues}>
5555 {({ handleSubmit }) => (
5656 <>
5757 <Grid.Item
src/views/Admin/AdminNetworkingKeys.js+21−17
@@ -31,12 +31,12 @@ import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
3131import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
3232import NoticeBox from 'components/NoticeBox';
3333
34+import { HexInput } from 'form/Inputs';
3435import {
3536 composeValidator,
3637 buildCheckboxValidator,
3738 buildHexValidator,
38− HexInput,
39−} from 'form/Inputs';
39+} from 'form/validators';
4040import BridgeForm from 'form/BridgeForm';
4141import Condition from 'form/Condition';
4242import FormError from 'form/FormError';
@@ -203,6 +203,14 @@ export default function AdminNetworkingKeys() {
203203 [construct, unconstruct]
204204 );
205205
206+ const initialValues = useMemo(
207+ () => ({
208+ useNetworkSeed: false,
209+ useDiscontinuity: false,
210+ }),
211+ []
212+ );
213+
206214 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
207215
208216 const renderTitle = () => {
@@ -325,11 +333,7 @@ export default function AdminNetworkingKeys() {
325333 <BridgeForm
326334 validate={validate}
327335 onValues={onValues}
328− onSubmit={() => {}}
329− initialValues={{
330− useNetworkSeed: false,
331− useDiscontinuity: false,
332− }}>
336+ initialValues={initialValues}>
333337 {({ handleSubmit }) => (
334338 <>
335339 <Grid.Item
@@ -361,21 +365,21 @@ export default function AdminNetworkingKeys() {
361365 label="Trigger New Continuity Era"
362366 disabled={inputsLocked}
363367 />
368+
369+ <Grid.Item full as={FormError} />
370+
371+ <Grid.Item
372+ full
373+ as={InlineEthereumTransaction}
374+ {...bind}
375+ label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
376+ onReturn={() => pop()}
377+ />
364378 </>
365379 )}
366380 </BridgeForm>
367381 )}
368382
369− <Grid.Item full as={FormError} />
370−
371− <Grid.Item
372− full
373− as={InlineEthereumTransaction}
374− {...bind}
375− label={`${hasKeys ? 'Reset' : 'Set'} Networking Keys`}
376− onReturn={() => pop()}
377− />
378−
379383 {isDefaultState && renderDetails()}
380384
381385 {completed && (
src/views/Admin/AdminSetProxy.js+14−13
@@ -21,12 +21,12 @@ import useEthereumTransaction from 'lib/useEthereumTransaction';
2121import ViewHeader from 'components/ViewHeader';
2222import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2323import { GAS_LIMITS } from 'lib/constants';
24+import { AddressInput } from 'form/Inputs';
2425import {
2526 composeValidator,
2627 buildCheckboxValidator,
2728 buildAddressValidator,
28− AddressInput,
29−} from 'form/Inputs';
29+} from 'form/validators';
3030import BridgeForm from 'form/BridgeForm';
3131import FormError from 'form/FormError';
3232
@@ -150,6 +150,8 @@ export default function AdminSetProxy() {
150150 [construct, unconstruct, unset]
151151 );
152152
153+ const initialValues = useMemo(() => ({ unset: false }), []);
154+
153155 const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
154156 const isProxySet = !isZeroAddress(proxyAddress);
155157
@@ -179,8 +181,7 @@ export default function AdminSetProxy() {
179181 <BridgeForm
180182 validate={validate}
181183 onValues={onValues}
182− onSubmit={() => {}}
183− initialValues={{ unset: false }}>
184+ initialValues={initialValues}>
184185 {({ handleSubmit, values }) => (
185186 <>
186187 <Grid.Item full as={Flex} row justify="between" align="center">
@@ -217,17 +218,17 @@ export default function AdminSetProxy() {
217218 disabled={inputsLocked || values.unset}
218219 />
219220 )}
220− </>
221− )}
222221
223− <Grid.Item full as={FormError} />
222+ <Grid.Item full as={FormError} />
224223
225− <Grid.Item
226− full
227− as={InlineEthereumTransaction}
228− {...bind}
229− onReturn={() => pop()}
230− />
224+ <Grid.Item
225+ full
226+ as={InlineEthereumTransaction}
227+ {...bind}
228+ onReturn={() => pop()}
229+ />
230+ </>
231+ )}
231232 </BridgeForm>
232233 </Grid>
233234 );
src/views/Admin/AdminTransfer.js+3−6
@@ -15,11 +15,8 @@ import { GAS_LIMITS } from 'lib/constants';
1515
1616import ViewHeader from 'components/ViewHeader';
1717import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18−import {
19− AddressInput,
20− composeValidator,
21− buildAddressValidator,
22−} from 'form/Inputs';
18+import { AddressInput } from 'form/Inputs';
19+import { composeValidator, buildAddressValidator } from 'form/validators';
2320import BridgeForm from 'form/BridgeForm';
2421import FormError from 'form/FormError';
2522
@@ -75,7 +72,7 @@ export default function AdminTransfer() {
7572 Transfer Point
7673 </Grid.Item>
7774
78− <BridgeForm validate={validate} onSubmit={() => {}} onValues={onValues}>
75+ <BridgeForm validate={validate} onValues={onValues}>
7976 {({ handleSubmit, values }) => (
8077 <>
8178 <Grid.Item
src/views/Admin/Reticket/ReticketVerify.js+11−9
@@ -6,11 +6,8 @@ import { validateExactly } from 'lib/validators';
66import { isDevelopment } from 'lib/flags';
77
88import BridgeForm from 'form/BridgeForm';
9−import {
10− TicketInput,
11− composeValidator,
12− buildTicketValidator,
13−} from 'form/Inputs';
9+import { TicketInput } from 'form/Inputs';
10+import { composeValidator, buildTicketValidator } from 'form/validators';
1411import SubmitButton from 'form/SubmitButton';
1512import FormError from 'form/FormError';
1613
@@ -30,6 +27,13 @@ export default function ReticketVerify({ newWallet }) {
3027 [ticket]
3128 );
3229
30+ const initialValues = useMemo(
31+ () => ({
32+ ticket: STUB_VERIFY_TICKET ? ticket : 'undefined',
33+ }),
34+ [ticket]
35+ );
36+
3337 const goExecute = useCallback(() => push(names.EXECUTE), [push, names]);
3438
3539 return (
@@ -39,10 +43,8 @@ export default function ReticketVerify({ newWallet }) {
3943 </Grid.Item>
4044 <BridgeForm
4145 validate={validate}
42− onSubmit={goExecute}
43− initialVaues={{
44− ticket: STUB_VERIFY_TICKET ? ticket : 'undefined',
45− }}>
46+ afterSubmit={goExecute}
47+ initialValues={initialValues}>
4648 {({ handleSubmit }) => (
4749 <>
4850 <Grid.Item
src/views/CreateGalaxy.js+3−3
@@ -17,12 +17,12 @@ import ViewHeader from 'components/ViewHeader';
1717import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
1818import View from 'components/View';
1919import BridgeForm from 'form/BridgeForm';
20+import { PointInput } from 'form/Inputs';
2021import {
21− PointInput,
2222 composeValidator,
2323 buildPointValidator,
2424 buildAddressValidator,
25−} from 'form/Inputs';
25+} from 'form/validators';
2626import FormError from 'form/FormError';
2727
2828function useCreateGalaxy() {
@@ -101,7 +101,7 @@ export default function CreateGalaxy() {
101101 Create a Galaxy
102102 </Grid.Item>
103103
104− <BridgeForm validate={validate} onSubmit={() => {}} onValues={onValues}>
104+ <BridgeForm validate={validate} onValues={onValues}>
105105 {({ handleSubmit, values }) => (
106106 <>
107107 {completed && (
src/views/Disclaimer.js+7−2
@@ -9,7 +9,7 @@ import View from 'components/View';
99import WarningBox from 'components/WarningBox';
1010import BridgeForm from 'form/BridgeForm';
1111import SubmitButton from 'form/SubmitButton';
12−import { composeValidator, buildCheckboxValidator } from 'form/Inputs';
12+import { composeValidator, buildCheckboxValidator } from 'form/validators';
1313
1414const TEXT_STYLE = 'f5';
1515
@@ -22,6 +22,8 @@ export default function ActivateDisclaimer() {
2222 []
2323 );
2424
25+ const initialValues = useMemo(() => ({ checkbox: false }), []);
26+
2527 const goBack = useCallback(async () => {
2628 setHasDisclaimed(true);
2729 pop();
@@ -78,7 +80,10 @@ export default function ActivateDisclaimer() {
7880 <Grid.Item full as={WarningBox}>
7981 Warning: Nobody but you can restore or reset your Master Ticket
8082 </Grid.Item>
81− <BridgeForm validate={validate} onSubmit={goBack}>
83+ <BridgeForm
84+ validate={validate}
85+ afterSubmit={goBack}
86+ initialValues={initialValues}>
8287 {({ handleSubmit }) => (
8388 <>
8489 <Grid.Item
src/views/Invite/InviteEmail.js+88−70
@@ -1,4 +1,10 @@
1−import React, { useCallback, useEffect, useRef, useState } from 'react';
1+import React, {
2+ useCallback,
3+ useEffect,
4+ useRef,
5+ useMemo,
6+ useState,
7+} from 'react';
28import cn from 'classnames';
39import * as ob from 'urbit-ob';
410import * as azimuth from 'azimuth-js';
@@ -39,7 +45,12 @@ import LoadableButton from 'components/LoadableButton';
3945import Highlighted from 'components/Highlighted';
4046import BridgeForm from 'form/BridgeForm';
4147import { Field } from 'react-final-form';
42−import { EmailInput, buildEmailValidator } from 'form/Inputs';
48+import { EmailInput } from 'form/Inputs';
49+import {
50+ buildEmailValidator,
51+ composeValidator,
52+ hasErrors,
53+} from 'form/validators';
4354import { FORM_ERROR } from 'final-form';
4455import SubmitButton from 'form/SubmitButton';
4556import FormError from 'form/FormError';
@@ -89,9 +100,7 @@ const buildAccessoryFor = (dones, errors) => name => {
89100
90101const emailFormatValidator = buildEmailValidator();
91102
92−// TODO: test with tank, successful txs
93103export default function InviteEmail() {
94− // TODO: resumption after error?
95104 const { contracts, web3, networkType } = useNetwork();
96105 const { wallet, walletType, walletHdPath } = useWallet();
97106 const { syncInvites, getInvites } = usePointCache();
@@ -126,41 +135,44 @@ export default function InviteEmail() {
126135 const isFailed = status === STATUS.FAILURE;
127136 const isDone = status === STATUS.SUCCESS;
128137
129− const validate = useCallback(
130− async values => {
131− // compute the list of valid emails
132− const emails = values.emails.filter(d => !!d);
133− if (uniq(emails).length !== emails.length) {
134− return { [FORM_ERROR]: 'Duplicate email.' };
138+ const validateEmail = useCallback(
139+ async email => {
140+ // check individual email validity
141+ const error = await emailFormatValidator(email);
142+ if (error) {
143+ return error;
135144 }
136145
137− let errors = {};
138− for (let i = 0; i < values.emails.length; i++) {
139− const name = `emails[${i}]`;
140− const email = values.emails[i];
141−
142− // check individual email validity
143− // TODO: validate each email individually using similar pattern from form/Inputs
144− // and then reutrn that
145− const error = await emailFormatValidator(email);
146− if (error) {
147− errors[name] = error;
148− continue;
149− }
150−
151− const hasReceived = await getHasReceived(email);
152− if (hasReceived) {
153− errors[name] = HAS_RECEIVED_TEXT;
154− continue;
155− }
146+ const hasReceived = await getHasReceived(email);
147+ if (hasReceived) {
148+ return HAS_RECEIVED_TEXT;
156149 }
157−
158− console.log(errors);
159− return errors;
160150 },
161151 [getHasReceived]
162152 );
163153
154+ const validateEmails = useCallback(
155+ async emails => await Promise.all(emails.map(validateEmail)),
156+ [validateEmail]
157+ );
158+
159+ const validateForm = useCallback(async (values, errors) => {
160+ if (hasErrors(errors)) {
161+ return errors;
162+ }
163+
164+ // check for email uniqenesss
165+ const emails = values.emails.filter(d => !!d);
166+ if (uniq(emails).length !== emails.length) {
167+ return { [FORM_ERROR]: 'Duplicate email.' };
168+ }
169+ }, []);
170+
171+ const validate = useMemo(
172+ () => composeValidator({ emails: validateEmails }, validateForm),
173+ [validateEmails, validateForm]
174+ );
175+
164176 // progress is [0, .length] of invites or receipts, as we're generating them
165177 const progress = isGenerating
166178 ? Object.keys(invites).length
@@ -179,7 +191,6 @@ export default function InviteEmail() {
179191
180192 const generateInvites = useCallback(
181193 async values => {
182− debugger;
183194 const _contracts = contracts.getOrElse(null);
184195 const _web3 = web3.getOrElse(null);
185196 const _wallet = wallet.getOrElse(null);
@@ -215,6 +226,7 @@ export default function InviteEmail() {
215226 for (let i = 0; i < values.emails.length; i++) {
216227 try {
217228 const email = values.emails[i];
229+ const planet = planets[i];
218230
219231 const { ticket, owner } = await wg.generateTemporaryTicketAndWallet(
220232 MIN_PLANET
@@ -224,40 +236,40 @@ export default function InviteEmail() {
224236 const inviteTx = azimuth.delegatedSending.sendPoint(
225237 _contracts,
226238 point,
227− planets[i],
239+ planet,
228240 owner.keys.address
229241 );
242+
230243 const signedTx = await signTransaction({
231244 wallet: _wallet,
232245 walletType,
233246 walletHdPath,
234247 networkType,
248+ chainId,
249+ nonce: nonce + i,
235250 // TODO: ^ make a useTransactionSigner to encapsulate this logic
236251 txn: inviteTx,
237252 gasPrice: DEFAULT_GAS_PRICE_GWEI.toString(),
238253 gasLimit: GAS_LIMIT.toString(),
239− nonce: nonce + i,
240− chainId,
241254 });
255+
242256 const rawTx = hexify(signedTx.serialize());
243257
244258 addInvite({ [email]: { email, ticket, signedTx, rawTx } });
245259 } catch (error) {
246260 console.error(error);
247261 errorCount++;
248− return {
249− [FORM_ERROR]: `Wallet Error: unable to generate invite wallets.`,
250− };
251262 }
252263 }
253264
254265 if (errorCount > 0) {
255− throw new Error(
256− `There ${pluralize(errorCount, 'was', 'were')} ${pluralize(
266+ return {
267+ [FORM_ERROR]: `There ${pluralize(
257268 errorCount,
258− 'error'
259− )} while generating wallets.`
260− );
269+ 'was',
270+ 'were'
271+ )} ${pluralize(errorCount, 'error')} while generating wallets.`,
272+ };
261273 }
262274 },
263275 [
@@ -275,6 +287,7 @@ export default function InviteEmail() {
275287 );
276288
277289 const sendInvites = useCallback(async () => {
290+ const emails = cachedEmails.current;
278291 const _web3 = web3.getOrElse(null);
279292 const _wallet = wallet.getOrElse(null);
280293 if (!_web3 || !_wallet) {
@@ -286,8 +299,8 @@ export default function InviteEmail() {
286299 _web3,
287300 point,
288301 _wallet.address,
289− INVITE_COST * cachedEmails.current.length,
290− cachedEmails.current.map(email => invites[email].rawTx),
302+ INVITE_COST * emails.length,
303+ emails.map(email => invites[email].rawTx),
291304 (address, minBalance, balance) =>
292305 setNeedFunds({ address, minBalance, balance }),
293306 () => setNeedFunds(undefined)
@@ -298,7 +311,7 @@ export default function InviteEmail() {
298311
299312 let unsentInvites = [];
300313 let orphanedInvites = [];
301− const txAndMailings = cachedEmails.current.map(async email => {
314+ const txAndMailings = emails.map(async email => {
302315 const invite = invites[email];
303316 try {
304317 const txHash = await sendSignedTransaction(
@@ -356,9 +369,12 @@ export default function InviteEmail() {
356369
357370 const onSubmit = useCallback(
358371 async values => {
359− setStatus(STATUS.GENERATING);
360− await generateInvites(values);
361372 cachedEmails.current = values.emails;
373+ setStatus(STATUS.GENERATING);
374+ const errors = await generateInvites(values);
375+ if (errors) {
376+ return errors;
377+ }
362378 setStatus(STATUS.CAN_SEND);
363379 },
364380 [generateInvites]
@@ -372,7 +388,7 @@ export default function InviteEmail() {
372388 setStatus(STATUS.SUCCESS);
373389 } catch (error) {
374390 console.error(error);
375− setGeneralError(error);
391+ setGeneralError(error.message);
376392 setStatus(STATUS.FAILURE);
377393 }
378394 }, [sendInvites]);
@@ -407,7 +423,7 @@ export default function InviteEmail() {
407423 </IconButton>
408424 </Grid.Item>
409425
410− {isDone && (
426+ {isDone ? (
411427 <>
412428 <Grid.Item as={Text} className="f5" full>
413429 <Highlighted>
@@ -419,13 +435,13 @@ export default function InviteEmail() {
419435
420436 {fields.map(name => (
421437 <Grid.Item as={HelpText} key={name} full>
422− <Field name={name}>{({ value }) => value}</Field>
438+ <Field name={name}>
439+ {({ input: { value } }) => value}
440+ </Field>
423441 </Grid.Item>
424442 ))}
425443 </>
426− )}
427−
428− {!isDone && (
444+ ) : (
429445 <>
430446 {fields.map((name, i) => {
431447 const isFirst = i === 0;
@@ -471,26 +487,28 @@ export default function InviteEmail() {
471487 );
472488 })}
473489
474− {canInput && (
490+ {canInput ? (
475491 <Grid.Item
476492 full
477493 as={SubmitButton}
478− handleSubmit={handleSubmit}>
494+ handleSubmit={handleSubmit}
495+ accessory={`${visualProgress}/${fields.length}`}>
496+ {buttonText(status, fields.length)}
497+ </Grid.Item>
498+ ) : (
499+ <Grid.Item
500+ full
501+ as={LoadableButton}
502+ className="mt4"
503+ disabled={!canSend}
504+ accessory={`${visualProgress}/${fields.length}`}
505+ onClick={doSend}
506+ success={canSend}
507+ solid>
479508 {buttonText(status, fields.length)}
480509 </Grid.Item>
481510 )}
482511
483− <Grid.Item
484− full
485− as={LoadableButton}
486− disabled={!(canInput || canSend || valid)}
487− accessory={`${visualProgress}/${fields.length}`}
488− onClick={doSend}
489− success={canSend}
490− solid>
491− {buttonText(status, fields.length)}
492− </Grid.Item>
493−
494512 {needFunds && (
495513 <Grid.Item full>
496514 <Highlighted warning>
@@ -505,8 +523,8 @@ export default function InviteEmail() {
505523 <Grid.Item full as={FormError} />
506524
507525 {generalError && (
508− <Grid.Item full>
509− <ErrorText>{generalError.message.toString()}</ErrorText>
526+ <Grid.Item full as={ErrorText}>
527+ {generalError}
510528 </Grid.Item>
511529 )}
512530 </>
src/views/IssueChild.js+3−4
@@ -19,13 +19,12 @@ import useConstant from 'lib/useConstant';
1919import ViewHeader from 'components/ViewHeader';
2020import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
2121import View from 'components/View';
22+import { PointInput, AddressInput } from 'form/Inputs';
2223import {
2324 composeValidator,
2425 buildPointValidator,
25− PointInput,
26− AddressInput,
2726 buildAddressValidator,
28−} from 'form/Inputs';
27+} from 'form/validators';
2928import BridgeForm from 'form/BridgeForm';
3029import FormError from 'form/FormError';
3130
@@ -128,7 +127,7 @@ export default function IssueChild() {
128127 </Grid.Item>
129128 )}
130129
131− <BridgeForm validate={validate} onSubmit={() => {}} onValues={onValues}>
130+ <BridgeForm validate={validate} onValues={onValues}>
132131 {({ handleSubmit, values }) => (
133132 <>
134133 {completed && (
src/views/Login/Keystore.js+4−4
@@ -8,19 +8,19 @@ import { useWallet } from 'store/wallet';
88import { EthereumWallet, WALLET_TYPES } from 'lib/wallet';
99import useLoginView from 'lib/useLoginView';
1010
11+import { PassphraseInput } from 'form/Inputs';
1112import {
1213 composeValidator,
1314 buildPassphraseValidator,
14− PassphraseInput,
1515 buildUploadValidator,
16−} from 'form/Inputs';
16+} from 'form/validators';
1717import UploadInput from 'form/UploadInput';
1818import ContinueButton from './ContinueButton';
1919import BridgeForm from 'form/BridgeForm';
2020import { FORM_ERROR } from 'final-form';
2121import FormError from 'form/FormError';
2222
23−export default function Keystore({ className }) {
23+export default function Keystore({ className, goHome }) {
2424 useLoginView(WALLET_TYPES.KEYSTORE);
2525
2626 // globals
@@ -61,7 +61,7 @@ export default function Keystore({ className }) {
6161 encrypted with a password, you'll also need to enter that below.
6262 </Grid.Item>
6363
64− <BridgeForm validate={validate} onSubmit={onSubmit}>
64+ <BridgeForm validate={validate} onSubmit={onSubmit} afterSubmit={goHome}>
6565 {({ handleSubmit }) => (
6666 <>
6767 <Grid.Item
src/views/Login/Ledger.js+17−12
@@ -28,15 +28,16 @@ import { WALLET_TYPES } from 'lib/wallet';
2828import useLoginView from 'lib/useLoginView';
2929import useBreakpoints from 'lib/useBreakpoints';
3030import {
31+ composeValidator,
3132 buildCheckboxValidator,
3233 buildHdPathValidator,
33− composeValidator,
3434 buildSelectValidator,
35−} from 'form/Inputs';
35+} from 'form/validators';
3636import BridgeForm from 'form/BridgeForm';
3737import FormError from 'form/FormError';
3838import ContinueButton from './ContinueButton';
3939import Condition from 'form/Condition';
40+import { FORM_ERROR } from 'final-form';
4041
4142const PATH_OPTIONS = [
4243 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
@@ -79,13 +80,11 @@ export default function Ledger({ className, goHome }) {
7980 setWallet(Just(hd));
8081 setWalletHdPath(addHdPrefix(values.hdpath));
8182 } catch (error) {
82− console.error(error);
8383 setWallet(Nothing());
84+ return { [FORM_ERROR]: error.message };
8485 }
85−
86− return await goHome();
8786 },
88− [goHome, setWallet, setWalletHdPath]
87+ [setWallet, setWalletHdPath]
8988 );
9089
9190 const onValues = useCallback(({ valid, values, form }) => {
@@ -103,6 +102,16 @@ export default function Ledger({ className, goHome }) {
103102 }
104103 }, []);
105104
105+ const initialValues = useMemo(
106+ () => ({
107+ useCustomPath: false,
108+ hdpath: PATH_OPTIONS[0].value.replace(/x/g, ACCOUNT_OPTIONS[0].value),
109+ derivationpath: PATH_OPTIONS[0].value,
110+ account: ACCOUNT_OPTIONS[0].value,
111+ }),
112+ []
113+ );
114+
106115 const full = useBreakpoints([true, true, false]);
107116 const half = useBreakpoints([false, false, true]);
108117 const isHTTPS = document.location.protocol === 'https:';
@@ -162,12 +171,8 @@ export default function Ledger({ className, goHome }) {
162171 validate={validate}
163172 onValues={onValues}
164173 onSubmit={onSubmit}
165− initialValues={{
166− useCustomPath: false,
167− hdpath: PATH_OPTIONS[0].value.replace(/x/g, ACCOUNT_OPTIONS[0].value),
168− derivationpath: PATH_OPTIONS[0].value,
169− account: ACCOUNT_OPTIONS[0].value,
170− }}>
174+ afterSubmit={goHome}
175+ initialValues={initialValues}>
171176 {({ handleSubmit }) => (
172177 <>
173178 <Condition when="useCustomPath" is={true}>
src/views/Login/Mnemonic.js+9−6
@@ -7,16 +7,14 @@ import { useWallet } from 'store/wallet';
77
88import { walletFromMnemonic, WALLET_TYPES } from 'lib/wallet';
99import useLoginView from 'lib/useLoginView';
10+import { MnemonicInput, HdPathInput, PassphraseInput } from 'form/Inputs';
1011import {
11− MnemonicInput,
12− HdPathInput,
13− PassphraseInput,
1412 composeValidator,
1513 buildMnemonicValidator,
1614 buildCheckboxValidator,
1715 buildPassphraseValidator,
1816 buildHdPathValidator,
19−} from 'form/Inputs';
17+} from 'form/validators';
2018import BridgeForm from 'form/BridgeForm';
2119import Condition from 'form/Condition';
2220import FormError from 'form/FormError';
@@ -60,13 +58,18 @@ export default function Mnemonic({ className, goHome }) {
6058 [setAuthMnemonic, setWallet, setWalletHdPath]
6159 );
6260
61+ const initialValues = useMemo(
62+ () => ({ hdpath: walletHdPath, useAdvanced: false }),
63+ [walletHdPath]
64+ );
65+
6366 return (
6467 <Grid className={cn('mt4', className)}>
6568 <BridgeForm
6669 validate={validate}
6770 onValues={onValues}
68− onSubmit={goHome}
69− initialValues={{ hdpath: walletHdPath, useAdvanced: false }}>
71+ afterSubmit={goHome}
72+ initialValues={initialValues}>
7073 {({ handleSubmit }) => (
7174 <>
7275 <Grid.Item
src/views/Login/PrivateKey.js+6−3
@@ -6,10 +6,13 @@ import { useWallet } from 'store/wallet';
66
77import { EthereumWallet, WALLET_TYPES, stripHexPrefix } from 'lib/wallet';
88import useLoginView from 'lib/useLoginView';
9+
910import FormError from 'form/FormError';
10−import ContinueButton from './ContinueButton';
1111import BridgeForm from 'form/BridgeForm';
12−import { buildHexValidator, composeValidator, HexInput } from 'form/Inputs';
12+import { HexInput } from 'form/Inputs';
13+import { buildHexValidator, composeValidator } from 'form/validators';
14+
15+import ContinueButton from './ContinueButton';
1316
1417export default function PrivateKey({ className, goHome }) {
1518 useLoginView(WALLET_TYPES.PRIVATE_KEY);
@@ -39,7 +42,7 @@ export default function PrivateKey({ className, goHome }) {
3942
4043 return (
4144 <Grid className={className}>
42− <BridgeForm validate={validate} onValues={onValues} onSubmit={goHome}>
45+ <BridgeForm validate={validate} onValues={onValues} afterSubmit={goHome}>
4346 {({ handleSubmit }) => (
4447 <>
4548 <Grid.Item
src/views/Login/Ticket.js+16−11
@@ -15,20 +15,20 @@ import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
1515import useImpliedPoint from 'lib/useImpliedPoint';
1616import useLoginView from 'lib/useLoginView';
1717import patp2dec from 'lib/patp2dec';
18+import timeout from 'lib/timeout';
19+
1820import BridgeForm from 'form/BridgeForm';
1921import Condition from 'form/Condition';
22+import { TicketInput, PassphraseInput, PointInput } from 'form/Inputs';
2023import {
21− TicketInput,
22− PassphraseInput,
23− PointInput,
2424 composeValidator,
2525 buildCheckboxValidator,
2626 buildTicketValidator,
2727 buildPassphraseValidator,
2828 buildPointValidator,
29−} from 'form/Inputs';
30−import timeout from 'lib/timeout';
29+} from 'form/validators';
3130import FormError from 'form/FormError';
31+
3232import ContinueButton from './ContinueButton';
3333
3434export default function Ticket({ className, goHome }) {
@@ -135,17 +135,22 @@ export default function Ticket({ className, goHome }) {
135135 [setPointCursor, setUrbitWallet]
136136 );
137137
138+ const initialValues = useMemo(
139+ () => ({
140+ point: impliedPoint || '',
141+ usePasshrase: false,
142+ useShards: false,
143+ }),
144+ [impliedPoint]
145+ );
146+
138147 return (
139148 <Grid className={cn('mt4', className)}>
140149 <BridgeForm
141150 validate={validate}
142151 onValues={onValues}
143− onSubmit={goHome}
144− initialValues={{
145− point: impliedPoint || '',
146− usePasshrase: false,
147− useShards: false,
148− }}>
152+ afterSubmit={goHome}
153+ initialValues={initialValues}>
149154 {({ handleSubmit }) => (
150155 <>
151156 <Grid.Item full as={PointInput} name="point" />
src/views/Login/Trezor.js+17−8
@@ -24,19 +24,21 @@ import {
2424 buildCheckboxValidator,
2525 buildSelectValidator,
2626 buildHdPathValidator,
27−} from 'form/Inputs';
27+} from 'form/validators';
2828import BridgeForm from 'form/BridgeForm';
2929import Condition from 'form/Condition';
30−import ContinueButton from './ContinueButton';
3130import FormError from 'form/FormError';
3231
32+import ContinueButton from './ContinueButton';
33+import { FORM_ERROR } from 'final-form';
34+
3335const ACCOUNT_OPTIONS = times(20, i => ({
3436 text: `Account #${i + 1}`,
3537 value: i,
3638}));
3739
3840// see Ledger.js for context — Trezor is basicaly Ledger with less complexity
39−export default function Trezor({ className }) {
41+export default function Trezor({ className, goHome }) {
4042 useLoginView(WALLET_TYPES.TREZOR);
4143
4244 const { setWallet, setWalletHdPath } = useWallet();
@@ -72,6 +74,7 @@ export default function Trezor({ className }) {
7274 setWalletHdPath(values.hdPath);
7375 } else {
7476 setWallet(Nothing());
77+ return { [FORM_ERROR]: 'Failed to authenticate with your Trezor.' };
7578 }
7679 },
7780 [setWallet, setWalletHdPath]
@@ -87,6 +90,15 @@ export default function Trezor({ className }) {
8790 }
8891 }, []);
8992
93+ const initialValues = useMemo(
94+ () => ({
95+ useCustomPath: false,
96+ hdpath: TREZOR_PATH.replace(/x/g, ACCOUNT_OPTIONS[0].value),
97+ account: ACCOUNT_OPTIONS[0].value,
98+ }),
99+ []
100+ );
101+
90102 return (
91103 <Grid className={className}>
92104 <Grid.Item full as={H5}>
@@ -102,11 +114,8 @@ export default function Trezor({ className }) {
102114 validate={validate}
103115 onValues={onValues}
104116 onSubmit={onSubmit}
105− initialValues={{
106− useCustomPath: false,
107− hdpath: TREZOR_PATH.replace(/x/g, ACCOUNT_OPTIONS[0].value),
108− account: ACCOUNT_OPTIONS[0].value,
109− }}>
117+ afterSubmit={goHome}
118+ initialValues={initialValues}>
110119 {({ handleSubmit }) => (
111120 <>
112121 <Condition when="useCustomPath" is={true}>
src/views/Party/PartySetPoolSize.js+8−7
@@ -11,21 +11,21 @@ import * as need from 'lib/need';
1111import useEthereumTransaction from 'lib/useEthereumTransaction';
1212import { GAS_LIMITS } from 'lib/constants';
1313import { useLocalRouter } from 'lib/LocalRouter';
14+import useCurrentPermissions from 'lib/useCurrentPermissions';
15+import patp2dec from 'lib/patp2dec';
1416
1517import ViewHeader from 'components/ViewHeader';
1618import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
17−import patp2dec from 'lib/patp2dec';
1819import View from 'components/View';
19−import useCurrentPermissions from 'lib/useCurrentPermissions';
2020import WarningBox from 'components/WarningBox';
21+
2122import BridgeForm from 'form/BridgeForm';
23+import { PointInput, NumberInput } from 'form/Inputs';
2224import {
23− PointInput,
2425 composeValidator,
25− NumberInput,
2626 buildNumberValidator,
2727 buildPointValidator,
28−} from 'form/Inputs';
28+} from 'form/validators';
2929import FormError from 'form/FormError';
3030
3131function useSetPoolSize() {
@@ -88,6 +88,8 @@ export default function PartySetPoolSize() {
8888 [construct, unconstruct]
8989 );
9090
91+ const initialValues = useMemo(() => ({ poolSize: 5 }), []);
92+
9193 return (
9294 <View pop={pop} inset>
9395 <Grid>
@@ -107,9 +109,8 @@ export default function PartySetPoolSize() {
107109
108110 <BridgeForm
109111 validate={validate}
110− onSubmit={() => {}}
111112 onValues={onValues}
112− initialValues={{ poolSize: 5 }}>
113+ initialValues={initialValues}>
113114 {({ handleSubmit, values }) => (
114115 <>
115116 <Grid.Item