on master help sign in
feat: finalize final-form migration
6526dd7Matt Condon (shrugs)
src/components/InlineEthereumTransaction.js+3−3
@@ -17,7 +17,7 @@ import { hexify } from 'lib/txn';
17
17
18import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
18import { GenerateButton, ForwardButton, RestartButton } from './Buttons';
19import WarningBox from './WarningBox';
19import WarningBox from './WarningBox';
20−import { composeValidator, buildCheckboxValidator } from 'form/Inputs';
20+import { composeValidator, buildCheckboxValidator } from 'form/validators';
21import BridgeForm from 'form/BridgeForm';
21import BridgeForm from 'form/BridgeForm';
22import Condition from 'form/Condition';
22import Condition from 'form/Condition';
23
23
@@ -123,7 +123,7 @@ export default function InlineEthereumTransaction({
123
123
124 return (
124 return (
125 <Grid className={cn(className, 'mt1')}>
125 <Grid className={cn(className, 'mt1')}>
126− <BridgeForm validate={validate} onValues={onValues} onSubmit={() => {}}>
126+ <BridgeForm validate={validate} onValues={onValues}>
127 {({ handleSubmit }) => (
127 {({ handleSubmit }) => (
128 <>
128 <>
129 {renderPrimaryButton()}
129 {renderPrimaryButton()}
@@ -151,7 +151,7 @@ export default function InlineEthereumTransaction({
151 name="useAdvanced"
151 name="useAdvanced"
152 label="Advanced Configuration"
152 label="Advanced Configuration"
153 inverseLabel="Cancel Advanced Configuration"
153 inverseLabel="Cancel Advanced Configuration"
154− disabled={!showConfigureInput}
154+ disabled={!showConfigureInput || initializing}
155 />
155 />
156
156
157 <Condition when="useAdvanced" is={true}>
157 <Condition when="useAdvanced" is={true}>
src/components/UploadButton.js+3−1
@@ -6,7 +6,9 @@ export default function UploadButton({ children, onChange, ...rest }) {
6
6
7 return (
7 return (
8 <label htmlFor="file" {...rest}>
8 <label htmlFor="file" {...rest}>
9− <Button accessory="↑">{children}</Button>
9+ <Button accessory="↑" solid>
10+ {children}
11+ </Button>
10 <input
12 <input
11 id="file"
13 id="file"
12 ref={button}
14 ref={button}
src/form/BridgeForm.js+33−3
@@ -1,14 +1,44 @@
1−import React from 'react';
1+import React, { useCallback } from 'react';
2import { Form } from 'react-final-form';
2import { Form } from 'react-final-form';
3import setFieldData from 'final-form-set-field-data';
3import setFieldData from 'final-form-set-field-data';
4import arrayMutators from 'final-form-arrays';
4import arrayMutators from 'final-form-arrays';
5+import { noop } from 'lodash';
5
6
6import ValuesHandler from './ValuesHandler';
7import ValuesHandler from './ValuesHandler';
7import ValidationPauser from './ValidationPauser';
8import ValidationPauser from './ValidationPauser';
8
9
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+ */
9−export default function BridgeForm({ children, onValues, ...rest }) {
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+
10 return (
37 return (
38+ <Form
11− <Form mutators={{ setFieldData, ...arrayMutators }} {...rest}>
39+ mutators={{ setFieldData, ...arrayMutators }}
40+ onSubmit={_onSubmit}
41+ {...rest}>
12 {formProps => (
42 {formProps => (
13 <>
43 <>
14 <ValidationPauser />
44 <ValidationPauser />
src/form/Inputs.js−99
@@ -1,23 +1,7 @@
1import React from 'react';
1import React from 'react';
2import { Input, AccessoryIcon } from 'indigo-react';
2import { Input, AccessoryIcon } from 'indigo-react';
3import { useField } from 'react-final-form';
3import { useField } from 'react-final-form';
4−import { some } from 'lodash';
5
4
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';
21import { prependSig, convertToNumber } from 'lib/transformers';
5import { prependSig, convertToNumber } from 'lib/transformers';
22import { DEFAULT_HD_PATH } from 'lib/wallet';
6import { DEFAULT_HD_PATH } from 'lib/wallet';
23import InputSigil from 'components/InputSigil';
7import InputSigil from 'components/InputSigil';
@@ -32,89 +16,6 @@ const PLACEHOLDER_PRIVATE_KEY =
32 '0x12345abcdee6beb2f323fab48b432925c9785808d33a6ca6d7ba00b45e9370c3';
16 '0x12345abcdee6beb2f323fab48b432925c9785808d33a6ca6d7ba00b45e9370c3';
33const PLACEHOLDER_EMAIL = 'Email Address';
17const PLACEHOLDER_EMAIL = 'Email Address';
34
18
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−
118export function TicketInput({ name, ...rest }) {
19export function TicketInput({ name, ...rest }) {
119 const {
20 const {
120 meta: { valid, error, validating, touched, active },
21 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({
44 setIsOpen(false);
44 setIsOpen(false);
45 };
45 };
46
46
47− console.log(input);
48 const text = options.find(o => o.value === input.value).text;
47 const text = options.find(o => o.value === input.value).text;
49
48
50 return (
49 return (
src/lib/useEthereumTransaction.js+12−4
@@ -189,6 +189,7 @@ export default function useEthereumTransaction(
189 ]);
189 ]);
190
190
191 useEffect(() => {
191 useEffect(() => {
192+ let mounted = true;
192 // if nonce or chainId is undefined, re-fetch on-chain info
193 // if nonce or chainId is undefined, re-fetch on-chain info
193 if (!(nonce === undefined || chainId === undefined)) {
194 if (!(nonce === undefined || chainId === undefined)) {
194 return;
195 return;
@@ -203,19 +204,26 @@ export default function useEthereumTransaction(
203 _web3.eth.getGasPrice(),
204 _web3.eth.getGasPrice(),
204 ]);
205 ]);
205
206
207+ const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
208+
209+ if (!mounted) {
210+ return;
211+ }
212+
206 setNonce(nonce);
213 setNonce(nonce);
207 setChainId(chainId);
214 setChainId(chainId);
208− const gasPrice = parseInt(fromWei(estimatedGasPrice, 'gwei'), 10);
209 setSuggestedGasPrice(gasPrice);
215 setSuggestedGasPrice(gasPrice);
210 setGasPrice(gasPrice);
216 setGasPrice(gasPrice);
211 } catch (error) {
217 } catch (error) {
212 setError(error);
218 setError(error);
213 }
219 }
214 })();
220 })();
221+
222+ return () => (mounted = false);
215 }, [_wallet, _web3, setError, nonce, chainId]);
223 }, [_wallet, _web3, setError, nonce, chainId]);
216
224
217 useEffect(() => {
225 useEffect(() => {
218− let cancelled = false;
226+ let mounted = true;
219
227
220 if (confirmed) {
228 if (confirmed) {
221 (async () => {
229 (async () => {
@@ -234,7 +242,7 @@ export default function useEthereumTransaction(
234 }
242 }
235 }
243 }
236
244
237− if (cancelled) {
245+ if (!mounted) {
238 return;
246 return;
239 }
247 }
240
248
@@ -242,7 +250,7 @@ export default function useEthereumTransaction(
242 })();
250 })();
243 }
251 }
244
252
245− return () => (cancelled = true);
253+ return () => (mounted = false);
246 }, [confirmed, refetch, completed, setError]);
254 }, [confirmed, refetch, completed, setError]);
247
255
248 const values = useDeepEqualReference({
256 const values = useDeepEqualReference({
src/lib/useRouter.js+2−2
@@ -53,7 +53,7 @@ export default function useRouter({
53 if (size <= 1) {
53 if (size <= 1) {
54 // if we are at the root, pass this event to our parent
54 // if we are at the root, pass this event to our parent
55 if (oldPopState.current) {
55 if (oldPopState.current) {
56− window.history.back();
56+ oldPopState.current();
57 }
57 }
58 return;
58 return;
59 }
59 }
@@ -108,7 +108,7 @@ export default function useRouter({
108
108
109 // construct new onpopstate handler
109 // construct new onpopstate handler
110 window.onpopstate = e => {
110 window.onpopstate = e => {
111− e.stopImmediatePropagation();
111+ e && e.stopImmediatePropagation();
112 pop();
112 pop();
113 };
113 };
114
114
src/views/Activate/ActivateCode.js+24−41
@@ -1,5 +1,5 @@
1−import React, { useCallback, useMemo, useRef } from 'react';
1+import React, { useCallback, useMemo } from 'react';
2−import { Just, Nothing } from 'folktale/maybe';
2+import { Just } from 'folktale/maybe';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4import { Grid, H4 } from 'indigo-react';
4import { Grid, H4 } from 'indigo-react';
5
5
@@ -24,12 +24,8 @@ import useHasDisclaimed from 'lib/useHasDisclaimed';
24
24
25import BridgeForm from 'form/BridgeForm';
25import BridgeForm from 'form/BridgeForm';
26import SubmitButton from 'form/SubmitButton';
26import SubmitButton from 'form/SubmitButton';
27−import {
28− TicketInput,
27+import { TicketInput } from 'form/Inputs';
29− hasErrors,
30− composeValidator,
31− buildTicketValidator,
28+import { composeValidator, buildTicketValidator } from 'form/validators';
32−} from 'form/Inputs';
33import FormError from 'form/FormError';
29import FormError from 'form/FormError';
34import { FORM_ERROR } from 'final-form';
30import { FORM_ERROR } from 'final-form';
35
31
@@ -40,9 +36,6 @@ export default function ActivateCode() {
40 const impliedTicket = useImpliedTicket();
36 const impliedTicket = useImpliedTicket();
41 const [hasDisclaimed] = useHasDisclaimed();
37 const [hasDisclaimed] = useHasDisclaimed();
42
38
43− const cachedInviteWallet = useRef(Nothing());
44− const cachedPoint = useRef();
45−
46 const {
39 const {
47 setDerivedWallet,
40 setDerivedWallet,
48 setInviteWallet,
41 setInviteWallet,
@@ -53,6 +46,7 @@ export default function ActivateCode() {
53 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
46 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
54 history,
47 history,
55 ]);
48 ]);
49+
56 const goToPassport = useCallback(() => {
50 const goToPassport = useCallback(() => {
57 push(names.PASSPORT);
51 push(names.PASSPORT);
58
52
@@ -61,23 +55,22 @@ export default function ActivateCode() {
61 }
55 }
62 }, [names, push, hasDisclaimed]);
56 }, [names, push, hasDisclaimed]);
63
57
64− // validate should be a pure function but we don't want to have to recompute
58+ const validate = useMemo(
65− // all of this information on submit, so cache the invite wallet and avoid
59+ () => composeValidator({ ticket: buildTicketValidator() }),
66− // re-renders that may trigger re-validations (causing infinite loop)
60+ []
67− const validateForm = useCallback(
61+ );
68− async ({ ticket }, errors) => {
69− if (hasErrors(errors)) {
70− return errors;
71− }
72
62
63+ // set our state on submission
64+ const onSubmit = useCallback(
65+ async values => {
73 await timeout(100); // allow the ui changes to flush before we lag it out
66 await timeout(100); // allow the ui changes to flush before we lag it out
74
67
75 const _contracts = need.contracts(contracts);
68 const _contracts = need.contracts(contracts);
76− const { seed } = await generateTemporaryOwnershipWallet(ticket);
69+ const { seed } = await generateTemporaryOwnershipWallet(values.ticket);
77
70
78− cachedInviteWallet.current = walletFromMnemonic(seed, DEFAULT_HD_PATH);
71+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
79
72
80− const _inviteWallet = need.wallet(cachedInviteWallet.current);
73+ const _inviteWallet = need.wallet(inviteWallet);
81
74
82 const owned = await azimuth.azimuth.getOwnedPoints(
75 const owned = await azimuth.azimuth.getOwnedPoints(
83 _contracts,
76 _contracts,
@@ -99,8 +92,10 @@ export default function ActivateCode() {
99 }
92 }
100
93
101 const point = parseInt(incoming[0], 10);
94 const point = parseInt(incoming[0], 10);
95+
102 setDerivedPoint(Just(point));
96 setDerivedPoint(Just(point));
103− cachedPoint.current = point;
97+ setInviteWallet(inviteWallet);
98+ setDerivedWallet(Just(await generateWallet(point)));
104 } else {
99 } else {
105 return {
100 return {
106 [FORM_ERROR]:
101 [FORM_ERROR]:
@@ -109,24 +104,12 @@ export default function ActivateCode() {
109 };
104 };
110 }
105 }
111 },
106 },
112− [contracts, setDerivedPoint]
107+ [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
113 );
108 );
114
109
115− const validate = useMemo(
110+ const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
116− () => composeValidator({ ticket: buildTicketValidator() }, validateForm),
111+ impliedTicket,
117− [validateForm]
112+ ]);
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]);
130
113
131 return (
114 return (
132 <View inset>
115 <View inset>
@@ -138,8 +121,8 @@ export default function ActivateCode() {
138 <BridgeForm
121 <BridgeForm
139 validate={validate}
122 validate={validate}
140 onSubmit={onSubmit}
123 onSubmit={onSubmit}
141− afterSubmit={afterSubmit}
124+ afterSubmit={goToPassport}
142− initialValues={{ ticket: impliedTicket || '' }}>
125+ initialValues={initialValues}>
143 {({ validating, submitting, handleSubmit }) => (
126 {({ validating, submitting, handleSubmit }) => (
144 <>
127 <>
145 <Grid.Item
128 <Grid.Item
src/views/Activate/PassportVerify.js+10−10
@@ -7,11 +7,8 @@ import { validateExactly } from 'lib/validators';
7import { isDevelopment } from 'lib/flags';
7import { isDevelopment } from 'lib/flags';
8
8
9import SubmitButton from 'form/SubmitButton';
9import SubmitButton from 'form/SubmitButton';
10−import {
11− TicketInput,
10+import { TicketInput } from 'form/Inputs';
12− composeValidator,
13− buildTicketValidator,
11+import { composeValidator, buildTicketValidator } from 'form/validators';
14−} from 'form/Inputs';
15import BridgeForm from 'form/BridgeForm';
12import BridgeForm from 'form/BridgeForm';
16
13
17import { useActivateFlow } from './ActivateFlow';
14import { useActivateFlow } from './ActivateFlow';
@@ -36,7 +33,12 @@ export default function PassportVerify({ className }) {
36 [ticket]
33 [ticket]
37 );
34 );
38
35
39− const onSubmit = useCallback(() => goToTransfer(), [goToTransfer]);
36+ const initialValues = useMemo(
37+ () => ({
38+ ticket: STUB_VERIFY_TICKET ? ticket : undefined,
39+ }),
40+ [ticket]
41+ );
40
42
41 return (
43 return (
42 <PassportView header="Verify Passport" step={2} className={className}>
44 <PassportView header="Verify Passport" step={2} className={className}>
@@ -48,10 +50,8 @@ export default function PassportVerify({ className }) {
48 </Grid.Item>
50 </Grid.Item>
49 <BridgeForm
51 <BridgeForm
50 validate={validate}
52 validate={validate}
51− onSubmit={onSubmit}
53+ afterSubmit={goToTransfer}
52− initialValues={{
54+ initialValues={initialValues}>
53− ticket: STUB_VERIFY_TICKET ? ticket : undefined,
54− }}>
55 {({ handleSubmit }) => (
55 {({ handleSubmit }) => (
56 <>
56 <>
57 <Grid.Item
57 <Grid.Item
src/views/Admin/AdminNetworkingKeys.js+21−17
@@ -31,12 +31,12 @@ import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
31import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
31import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
32import NoticeBox from 'components/NoticeBox';
32import NoticeBox from 'components/NoticeBox';
33
33
34+import { HexInput } from 'form/Inputs';
34import {
35import {
35 composeValidator,
36 composeValidator,
36 buildCheckboxValidator,
37 buildCheckboxValidator,
37 buildHexValidator,
38 buildHexValidator,
38− HexInput,
39−} from 'form/Inputs';
39+} from 'form/validators';
40import BridgeForm from 'form/BridgeForm';
40import BridgeForm from 'form/BridgeForm';
41import Condition from 'form/Condition';
41import Condition from 'form/Condition';
42import FormError from 'form/FormError';
42import FormError from 'form/FormError';
@@ -203,6 +203,14 @@ export default function AdminNetworkingKeys() {
203 [construct, unconstruct]
203 [construct, unconstruct]
204 );
204 );
205
205
206+ const initialValues = useMemo(
207+ () => ({
208+ useNetworkSeed: false,
209+ useDiscontinuity: false,
210+ }),
211+ []
212+ );
213+
206 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
214 const goRelocate = useCallback(() => push(names.RELOCATE), [push, names]);
207
215
208 const renderTitle = () => {
216 const renderTitle = () => {
@@ -325,11 +333,7 @@ export default function AdminNetworkingKeys() {
325 <BridgeForm
333 <BridgeForm
326 validate={validate}
334 validate={validate}
327 onValues={onValues}
335 onValues={onValues}
328− onSubmit={() => {}}
329− initialValues={{
336+ initialValues={initialValues}>
330− useNetworkSeed: false,
331− useDiscontinuity: false,
332− }}>
333 {({ handleSubmit }) => (
337 {({ handleSubmit }) => (
334 <>
338 <>
335 <Grid.Item
339 <Grid.Item
@@ -361,21 +365,21 @@ export default function AdminNetworkingKeys() {
361 label="Trigger New Continuity Era"
365 label="Trigger New Continuity Era"
362 disabled={inputsLocked}
366 disabled={inputsLocked}
363 />
367 />
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+ />
364 </>
378 </>
365 )}
379 )}
366 </BridgeForm>
380 </BridgeForm>
367 )}
381 )}
368
382
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−
379 {isDefaultState && renderDetails()}
383 {isDefaultState && renderDetails()}
380
384
381 {completed && (
385 {completed && (
src/views/Admin/AdminSetProxy.js+14−13
@@ -21,12 +21,12 @@ import useEthereumTransaction from 'lib/useEthereumTransaction';
21import ViewHeader from 'components/ViewHeader';
21import ViewHeader from 'components/ViewHeader';
22import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
22import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
23import { GAS_LIMITS } from 'lib/constants';
23import { GAS_LIMITS } from 'lib/constants';
24+import { AddressInput } from 'form/Inputs';
24import {
25import {
25 composeValidator,
26 composeValidator,
26 buildCheckboxValidator,
27 buildCheckboxValidator,
27 buildAddressValidator,
28 buildAddressValidator,
28− AddressInput,
29−} from 'form/Inputs';
29+} from 'form/validators';
30import BridgeForm from 'form/BridgeForm';
30import BridgeForm from 'form/BridgeForm';
31import FormError from 'form/FormError';
31import FormError from 'form/FormError';
32
32
@@ -150,6 +150,8 @@ export default function AdminSetProxy() {
150 [construct, unconstruct, unset]
150 [construct, unconstruct, unset]
151 );
151 );
152
152
153+ const initialValues = useMemo(() => ({ unset: false }), []);
154+
153 const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
155 const proxyAddress = proxyFromDetails(_details, _contracts, data.proxyType);
154 const isProxySet = !isZeroAddress(proxyAddress);
156 const isProxySet = !isZeroAddress(proxyAddress);
155
157
@@ -179,8 +181,7 @@ export default function AdminSetProxy() {
179 <BridgeForm
181 <BridgeForm
180 validate={validate}
182 validate={validate}
181 onValues={onValues}
183 onValues={onValues}
182− onSubmit={() => {}}
183− initialValues={{ unset: false }}>
184+ initialValues={initialValues}>
184 {({ handleSubmit, values }) => (
185 {({ handleSubmit, values }) => (
185 <>
186 <>
186 <Grid.Item full as={Flex} row justify="between" align="center">
187 <Grid.Item full as={Flex} row justify="between" align="center">
@@ -217,17 +218,17 @@ export default function AdminSetProxy() {
217 disabled={inputsLocked || values.unset}
218 disabled={inputsLocked || values.unset}
218 />
219 />
219 )}
220 )}
220− </>
221− )}
222
221
223− <Grid.Item full as={FormError} />
222+ <Grid.Item full as={FormError} />
224
223
225− <Grid.Item
224+ <Grid.Item
226− full
225+ full
227− as={InlineEthereumTransaction}
226+ as={InlineEthereumTransaction}
228− {...bind}
227+ {...bind}
229− onReturn={() => pop()}
228+ onReturn={() => pop()}
230− />
229+ />
230+ </>
231+ )}
231 </BridgeForm>
232 </BridgeForm>
232 </Grid>
233 </Grid>
233 );
234 );
src/views/Admin/AdminTransfer.js+3−6
@@ -15,11 +15,8 @@ import { GAS_LIMITS } from 'lib/constants';
15
15
16import ViewHeader from 'components/ViewHeader';
16import ViewHeader from 'components/ViewHeader';
17import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
17import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18−import {
19− AddressInput,
18+import { AddressInput } from 'form/Inputs';
20− composeValidator,
21− buildAddressValidator,
19+import { composeValidator, buildAddressValidator } from 'form/validators';
22−} from 'form/Inputs';
23import BridgeForm from 'form/BridgeForm';
20import BridgeForm from 'form/BridgeForm';
24import FormError from 'form/FormError';
21import FormError from 'form/FormError';
25
22
@@ -75,7 +72,7 @@ export default function AdminTransfer() {
75 Transfer Point
72 Transfer Point
76 </Grid.Item>
73 </Grid.Item>
77
74
78− <BridgeForm validate={validate} onSubmit={() => {}} onValues={onValues}>
75+ <BridgeForm validate={validate} onValues={onValues}>
79 {({ handleSubmit, values }) => (
76 {({ handleSubmit, values }) => (
80 <>
77 <>
81 <Grid.Item
78 <Grid.Item
src/views/Admin/Reticket/ReticketVerify.js+11−9
@@ -6,11 +6,8 @@ import { validateExactly } from 'lib/validators';
6import { isDevelopment } from 'lib/flags';
6import { isDevelopment } from 'lib/flags';
7
7
8import BridgeForm from 'form/BridgeForm';
8import BridgeForm from 'form/BridgeForm';
9−import {
10− TicketInput,
9+import { TicketInput } from 'form/Inputs';
11− composeValidator,
12− buildTicketValidator,
10+import { composeValidator, buildTicketValidator } from 'form/validators';
13−} from 'form/Inputs';
14import SubmitButton from 'form/SubmitButton';
11import SubmitButton from 'form/SubmitButton';
15import FormError from 'form/FormError';
12import FormError from 'form/FormError';
16
13
@@ -30,6 +27,13 @@ export default function ReticketVerify({ newWallet }) {
30 [ticket]
27 [ticket]
31 );
28 );
32
29
30+ const initialValues = useMemo(
31+ () => ({
32+ ticket: STUB_VERIFY_TICKET ? ticket : 'undefined',
33+ }),
34+ [ticket]
35+ );
36+
33 const goExecute = useCallback(() => push(names.EXECUTE), [push, names]);
37 const goExecute = useCallback(() => push(names.EXECUTE), [push, names]);
34
38
35 return (
39 return (
@@ -39,10 +43,8 @@ export default function ReticketVerify({ newWallet }) {
39 </Grid.Item>
43 </Grid.Item>
40 <BridgeForm
44 <BridgeForm
41 validate={validate}
45 validate={validate}
42− onSubmit={goExecute}
46+ afterSubmit={goExecute}
43− initialVaues={{
47+ initialValues={initialValues}>
44− ticket: STUB_VERIFY_TICKET ? ticket : 'undefined',
45− }}>
46 {({ handleSubmit }) => (
48 {({ handleSubmit }) => (
47 <>
49 <>
48 <Grid.Item
50 <Grid.Item
src/views/CreateGalaxy.js+3−3
@@ -17,12 +17,12 @@ import ViewHeader from 'components/ViewHeader';
17import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
17import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18import View from 'components/View';
18import View from 'components/View';
19import BridgeForm from 'form/BridgeForm';
19import BridgeForm from 'form/BridgeForm';
20+import { PointInput } from 'form/Inputs';
20import {
21import {
21− PointInput,
22 composeValidator,
22 composeValidator,
23 buildPointValidator,
23 buildPointValidator,
24 buildAddressValidator,
24 buildAddressValidator,
25−} from 'form/Inputs';
25+} from 'form/validators';
26import FormError from 'form/FormError';
26import FormError from 'form/FormError';
27
27
28function useCreateGalaxy() {
28function useCreateGalaxy() {
@@ -101,7 +101,7 @@ export default function CreateGalaxy() {
101 Create a Galaxy
101 Create a Galaxy
102 </Grid.Item>
102 </Grid.Item>
103
103
104− <BridgeForm validate={validate} onSubmit={() => {}} onValues={onValues}>
104+ <BridgeForm validate={validate} onValues={onValues}>
105 {({ handleSubmit, values }) => (
105 {({ handleSubmit, values }) => (
106 <>
106 <>
107 {completed && (
107 {completed && (
src/views/Disclaimer.js+7−2
@@ -9,7 +9,7 @@ import View from 'components/View';
9import WarningBox from 'components/WarningBox';
9import WarningBox from 'components/WarningBox';
10import BridgeForm from 'form/BridgeForm';
10import BridgeForm from 'form/BridgeForm';
11import SubmitButton from 'form/SubmitButton';
11import SubmitButton from 'form/SubmitButton';
12−import { composeValidator, buildCheckboxValidator } from 'form/Inputs';
12+import { composeValidator, buildCheckboxValidator } from 'form/validators';
13
13
14const TEXT_STYLE = 'f5';
14const TEXT_STYLE = 'f5';
15
15
@@ -22,6 +22,8 @@ export default function ActivateDisclaimer() {
22 []
22 []
23 );
23 );
24
24
25+ const initialValues = useMemo(() => ({ checkbox: false }), []);
26+
25 const goBack = useCallback(async () => {
27 const goBack = useCallback(async () => {
26 setHasDisclaimed(true);
28 setHasDisclaimed(true);
27 pop();
29 pop();
@@ -78,7 +80,10 @@ export default function ActivateDisclaimer() {
78 <Grid.Item full as={WarningBox}>
80 <Grid.Item full as={WarningBox}>
79 Warning: Nobody but you can restore or reset your Master Ticket
81 Warning: Nobody but you can restore or reset your Master Ticket
80 </Grid.Item>
82 </Grid.Item>
83+ <BridgeForm
81− <BridgeForm validate={validate} onSubmit={goBack}>
84+ validate={validate}
85+ afterSubmit={goBack}
86+ initialValues={initialValues}>
82 {({ handleSubmit }) => (
87 {({ handleSubmit }) => (
83 <>
88 <>
84 <Grid.Item
89 <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';
2import cn from 'classnames';
8import cn from 'classnames';
3import * as ob from 'urbit-ob';
9import * as ob from 'urbit-ob';
4import * as azimuth from 'azimuth-js';
10import * as azimuth from 'azimuth-js';
@@ -39,7 +45,12 @@ import LoadableButton from 'components/LoadableButton';
39import Highlighted from 'components/Highlighted';
45import Highlighted from 'components/Highlighted';
40import BridgeForm from 'form/BridgeForm';
46import BridgeForm from 'form/BridgeForm';
41import { Field } from 'react-final-form';
47import { 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';
43import { FORM_ERROR } from 'final-form';
54import { FORM_ERROR } from 'final-form';
44import SubmitButton from 'form/SubmitButton';
55import SubmitButton from 'form/SubmitButton';
45import FormError from 'form/FormError';
56import FormError from 'form/FormError';
@@ -89,9 +100,7 @@ const buildAccessoryFor = (dones, errors) => name => {
89
100
90const emailFormatValidator = buildEmailValidator();
101const emailFormatValidator = buildEmailValidator();
91
102
92−// TODO: test with tank, successful txs
93export default function InviteEmail() {
103export default function InviteEmail() {
94− // TODO: resumption after error?
95 const { contracts, web3, networkType } = useNetwork();
104 const { contracts, web3, networkType } = useNetwork();
96 const { wallet, walletType, walletHdPath } = useWallet();
105 const { wallet, walletType, walletHdPath } = useWallet();
97 const { syncInvites, getInvites } = usePointCache();
106 const { syncInvites, getInvites } = usePointCache();
@@ -126,41 +135,44 @@ export default function InviteEmail() {
126 const isFailed = status === STATUS.FAILURE;
135 const isFailed = status === STATUS.FAILURE;
127 const isDone = status === STATUS.SUCCESS;
136 const isDone = status === STATUS.SUCCESS;
128
137
129− const validate = useCallback(
138+ const validateEmail = useCallback(
130− async values => {
139+ async email => {
131− // compute the list of valid emails
140+ // check individual email validity
132− const emails = values.emails.filter(d => !!d);
141+ const error = await emailFormatValidator(email);
133− if (uniq(emails).length !== emails.length) {
142+ if (error) {
134− return { [FORM_ERROR]: 'Duplicate email.' };
143+ return error;
135 }
144 }
136
145
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);
146+ const hasReceived = await getHasReceived(email);
152− if (hasReceived) {
147+ if (hasReceived) {
153− errors[name] = HAS_RECEIVED_TEXT;
148+ return HAS_RECEIVED_TEXT;
154− continue;
155− }
156 }
149 }
157−
158− console.log(errors);
159− return errors;
160 },
150 },
161 [getHasReceived]
151 [getHasReceived]
162 );
152 );
163
153
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+
164 // progress is [0, .length] of invites or receipts, as we're generating them
176 // progress is [0, .length] of invites or receipts, as we're generating them
165 const progress = isGenerating
177 const progress = isGenerating
166 ? Object.keys(invites).length
178 ? Object.keys(invites).length
@@ -179,7 +191,6 @@ export default function InviteEmail() {
179
191
180 const generateInvites = useCallback(
192 const generateInvites = useCallback(
181 async values => {
193 async values => {
182− debugger;
183 const _contracts = contracts.getOrElse(null);
194 const _contracts = contracts.getOrElse(null);
184 const _web3 = web3.getOrElse(null);
195 const _web3 = web3.getOrElse(null);
185 const _wallet = wallet.getOrElse(null);
196 const _wallet = wallet.getOrElse(null);
@@ -215,6 +226,7 @@ export default function InviteEmail() {
215 for (let i = 0; i < values.emails.length; i++) {
226 for (let i = 0; i < values.emails.length; i++) {
216 try {
227 try {
217 const email = values.emails[i];
228 const email = values.emails[i];
229+ const planet = planets[i];
218
230
219 const { ticket, owner } = await wg.generateTemporaryTicketAndWallet(
231 const { ticket, owner } = await wg.generateTemporaryTicketAndWallet(
220 MIN_PLANET
232 MIN_PLANET
@@ -224,40 +236,40 @@ export default function InviteEmail() {
224 const inviteTx = azimuth.delegatedSending.sendPoint(
236 const inviteTx = azimuth.delegatedSending.sendPoint(
225 _contracts,
237 _contracts,
226 point,
238 point,
227− planets[i],
239+ planet,
228 owner.keys.address
240 owner.keys.address
229 );
241 );
242+
230 const signedTx = await signTransaction({
243 const signedTx = await signTransaction({
231 wallet: _wallet,
244 wallet: _wallet,
232 walletType,
245 walletType,
233 walletHdPath,
246 walletHdPath,
234 networkType,
247 networkType,
248+ chainId,
249+ nonce: nonce + i,
235 // TODO: ^ make a useTransactionSigner to encapsulate this logic
250 // TODO: ^ make a useTransactionSigner to encapsulate this logic
236 txn: inviteTx,
251 txn: inviteTx,
237 gasPrice: DEFAULT_GAS_PRICE_GWEI.toString(),
252 gasPrice: DEFAULT_GAS_PRICE_GWEI.toString(),
238 gasLimit: GAS_LIMIT.toString(),
253 gasLimit: GAS_LIMIT.toString(),
239− nonce: nonce + i,
240− chainId,
241 });
254 });
255+
242 const rawTx = hexify(signedTx.serialize());
256 const rawTx = hexify(signedTx.serialize());
243
257
244 addInvite({ [email]: { email, ticket, signedTx, rawTx } });
258 addInvite({ [email]: { email, ticket, signedTx, rawTx } });
245 } catch (error) {
259 } catch (error) {
246 console.error(error);
260 console.error(error);
247 errorCount++;
261 errorCount++;
248− return {
249− [FORM_ERROR]: `Wallet Error: unable to generate invite wallets.`,
250− };
251 }
262 }
252 }
263 }
253
264
254 if (errorCount > 0) {
265 if (errorCount > 0) {
255− throw new Error(
266+ return {
256− `There ${pluralize(errorCount, 'was', 'were')} ${pluralize(
267+ [FORM_ERROR]: `There ${pluralize(
257 errorCount,
268 errorCount,
258− 'error'
269+ 'was',
270+ 'were'
259− )} while generating wallets.`
271+ )} ${pluralize(errorCount, 'error')} while generating wallets.`,
260− );
272+ };
261 }
273 }
262 },
274 },
263 [
275 [
@@ -275,6 +287,7 @@ export default function InviteEmail() {
275 );
287 );
276
288
277 const sendInvites = useCallback(async () => {
289 const sendInvites = useCallback(async () => {
290+ const emails = cachedEmails.current;
278 const _web3 = web3.getOrElse(null);
291 const _web3 = web3.getOrElse(null);
279 const _wallet = wallet.getOrElse(null);
292 const _wallet = wallet.getOrElse(null);
280 if (!_web3 || !_wallet) {
293 if (!_web3 || !_wallet) {
@@ -286,8 +299,8 @@ export default function InviteEmail() {
286 _web3,
299 _web3,
287 point,
300 point,
288 _wallet.address,
301 _wallet.address,
289− INVITE_COST * cachedEmails.current.length,
302+ INVITE_COST * emails.length,
290− cachedEmails.current.map(email => invites[email].rawTx),
303+ emails.map(email => invites[email].rawTx),
291 (address, minBalance, balance) =>
304 (address, minBalance, balance) =>
292 setNeedFunds({ address, minBalance, balance }),
305 setNeedFunds({ address, minBalance, balance }),
293 () => setNeedFunds(undefined)
306 () => setNeedFunds(undefined)
@@ -298,7 +311,7 @@ export default function InviteEmail() {
298
311
299 let unsentInvites = [];
312 let unsentInvites = [];
300 let orphanedInvites = [];
313 let orphanedInvites = [];
301− const txAndMailings = cachedEmails.current.map(async email => {
314+ const txAndMailings = emails.map(async email => {
302 const invite = invites[email];
315 const invite = invites[email];
303 try {
316 try {
304 const txHash = await sendSignedTransaction(
317 const txHash = await sendSignedTransaction(
@@ -356,9 +369,12 @@ export default function InviteEmail() {
356
369
357 const onSubmit = useCallback(
370 const onSubmit = useCallback(
358 async values => {
371 async values => {
359− setStatus(STATUS.GENERATING);
360− await generateInvites(values);
361 cachedEmails.current = values.emails;
372 cachedEmails.current = values.emails;
373+ setStatus(STATUS.GENERATING);
374+ const errors = await generateInvites(values);
375+ if (errors) {
376+ return errors;
377+ }
362 setStatus(STATUS.CAN_SEND);
378 setStatus(STATUS.CAN_SEND);
363 },
379 },
364 [generateInvites]
380 [generateInvites]
@@ -372,7 +388,7 @@ export default function InviteEmail() {
372 setStatus(STATUS.SUCCESS);
388 setStatus(STATUS.SUCCESS);
373 } catch (error) {
389 } catch (error) {
374 console.error(error);
390 console.error(error);
375− setGeneralError(error);
391+ setGeneralError(error.message);
376 setStatus(STATUS.FAILURE);
392 setStatus(STATUS.FAILURE);
377 }
393 }
378 }, [sendInvites]);
394 }, [sendInvites]);
@@ -407,7 +423,7 @@ export default function InviteEmail() {
407 </IconButton>
423 </IconButton>
408 </Grid.Item>
424 </Grid.Item>
409
425
410− {isDone && (
426+ {isDone ? (
411 <>
427 <>
412 <Grid.Item as={Text} className="f5" full>
428 <Grid.Item as={Text} className="f5" full>
413 <Highlighted>
429 <Highlighted>
@@ -419,13 +435,13 @@ export default function InviteEmail() {
419
435
420 {fields.map(name => (
436 {fields.map(name => (
421 <Grid.Item as={HelpText} key={name} full>
437 <Grid.Item as={HelpText} key={name} full>
422− <Field name={name}>{({ value }) => value}</Field>
438+ <Field name={name}>
439+ {({ input: { value } }) => value}
440+ </Field>
423 </Grid.Item>
441 </Grid.Item>
424 ))}
442 ))}
425 </>
443 </>
426− )}
444+ ) : (
427−
428− {!isDone && (
429 <>
445 <>
430 {fields.map((name, i) => {
446 {fields.map((name, i) => {
431 const isFirst = i === 0;
447 const isFirst = i === 0;
@@ -471,26 +487,28 @@ export default function InviteEmail() {
471 );
487 );
472 })}
488 })}
473
489
474− {canInput && (
490+ {canInput ? (
475 <Grid.Item
491 <Grid.Item
476 full
492 full
477 as={SubmitButton}
493 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>
479 {buttonText(status, fields.length)}
508 {buttonText(status, fields.length)}
480 </Grid.Item>
509 </Grid.Item>
481 )}
510 )}
482
511
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−
494 {needFunds && (
512 {needFunds && (
495 <Grid.Item full>
513 <Grid.Item full>
496 <Highlighted warning>
514 <Highlighted warning>
@@ -505,8 +523,8 @@ export default function InviteEmail() {
505 <Grid.Item full as={FormError} />
523 <Grid.Item full as={FormError} />
506
524
507 {generalError && (
525 {generalError && (
508− <Grid.Item full>
526+ <Grid.Item full as={ErrorText}>
509− <ErrorText>{generalError.message.toString()}</ErrorText>
527+ {generalError}
510 </Grid.Item>
528 </Grid.Item>
511 )}
529 )}
512 </>
530 </>
src/views/IssueChild.js+3−4
@@ -19,13 +19,12 @@ import useConstant from 'lib/useConstant';
19import ViewHeader from 'components/ViewHeader';
19import ViewHeader from 'components/ViewHeader';
20import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
20import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
21import View from 'components/View';
21import View from 'components/View';
22+import { PointInput, AddressInput } from 'form/Inputs';
22import {
23import {
23 composeValidator,
24 composeValidator,
24 buildPointValidator,
25 buildPointValidator,
25− PointInput,
26− AddressInput,
27 buildAddressValidator,
26 buildAddressValidator,
28−} from 'form/Inputs';
27+} from 'form/validators';
29import BridgeForm from 'form/BridgeForm';
28import BridgeForm from 'form/BridgeForm';
30import FormError from 'form/FormError';
29import FormError from 'form/FormError';
31
30
@@ -128,7 +127,7 @@ export default function IssueChild() {
128 </Grid.Item>
127 </Grid.Item>
129 )}
128 )}
130
129
131− <BridgeForm validate={validate} onSubmit={() => {}} onValues={onValues}>
130+ <BridgeForm validate={validate} onValues={onValues}>
132 {({ handleSubmit, values }) => (
131 {({ handleSubmit, values }) => (
133 <>
132 <>
134 {completed && (
133 {completed && (
src/views/Login/Keystore.js+4−4
@@ -8,19 +8,19 @@ import { useWallet } from 'store/wallet';
8import { EthereumWallet, WALLET_TYPES } from 'lib/wallet';
8import { EthereumWallet, WALLET_TYPES } from 'lib/wallet';
9import useLoginView from 'lib/useLoginView';
9import useLoginView from 'lib/useLoginView';
10
10
11+import { PassphraseInput } from 'form/Inputs';
11import {
12import {
12 composeValidator,
13 composeValidator,
13 buildPassphraseValidator,
14 buildPassphraseValidator,
14− PassphraseInput,
15 buildUploadValidator,
15 buildUploadValidator,
16−} from 'form/Inputs';
16+} from 'form/validators';
17import UploadInput from 'form/UploadInput';
17import UploadInput from 'form/UploadInput';
18import ContinueButton from './ContinueButton';
18import ContinueButton from './ContinueButton';
19import BridgeForm from 'form/BridgeForm';
19import BridgeForm from 'form/BridgeForm';
20import { FORM_ERROR } from 'final-form';
20import { FORM_ERROR } from 'final-form';
21import FormError from 'form/FormError';
21import FormError from 'form/FormError';
22
22
23−export default function Keystore({ className }) {
23+export default function Keystore({ className, goHome }) {
24 useLoginView(WALLET_TYPES.KEYSTORE);
24 useLoginView(WALLET_TYPES.KEYSTORE);
25
25
26 // globals
26 // globals
@@ -61,7 +61,7 @@ export default function Keystore({ className }) {
61 encrypted with a password, you'll also need to enter that below.
61 encrypted with a password, you'll also need to enter that below.
62 </Grid.Item>
62 </Grid.Item>
63
63
64− <BridgeForm validate={validate} onSubmit={onSubmit}>
64+ <BridgeForm validate={validate} onSubmit={onSubmit} afterSubmit={goHome}>
65 {({ handleSubmit }) => (
65 {({ handleSubmit }) => (
66 <>
66 <>
67 <Grid.Item
67 <Grid.Item
src/views/Login/Ledger.js+17−12
@@ -28,15 +28,16 @@ import { WALLET_TYPES } from 'lib/wallet';
28import useLoginView from 'lib/useLoginView';
28import useLoginView from 'lib/useLoginView';
29import useBreakpoints from 'lib/useBreakpoints';
29import useBreakpoints from 'lib/useBreakpoints';
30import {
30import {
31+ composeValidator,
31 buildCheckboxValidator,
32 buildCheckboxValidator,
32 buildHdPathValidator,
33 buildHdPathValidator,
33− composeValidator,
34 buildSelectValidator,
34 buildSelectValidator,
35−} from 'form/Inputs';
35+} from 'form/validators';
36import BridgeForm from 'form/BridgeForm';
36import BridgeForm from 'form/BridgeForm';
37import FormError from 'form/FormError';
37import FormError from 'form/FormError';
38import ContinueButton from './ContinueButton';
38import ContinueButton from './ContinueButton';
39import Condition from 'form/Condition';
39import Condition from 'form/Condition';
40+import { FORM_ERROR } from 'final-form';
40
41
41const PATH_OPTIONS = [
42const PATH_OPTIONS = [
42 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
43 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
@@ -79,13 +80,11 @@ export default function Ledger({ className, goHome }) {
79 setWallet(Just(hd));
80 setWallet(Just(hd));
80 setWalletHdPath(addHdPrefix(values.hdpath));
81 setWalletHdPath(addHdPrefix(values.hdpath));
81 } catch (error) {
82 } catch (error) {
82− console.error(error);
83 setWallet(Nothing());
83 setWallet(Nothing());
84+ return { [FORM_ERROR]: error.message };
84 }
85 }
85−
86− return await goHome();
87 },
86 },
88− [goHome, setWallet, setWalletHdPath]
87+ [setWallet, setWalletHdPath]
89 );
88 );
90
89
91 const onValues = useCallback(({ valid, values, form }) => {
90 const onValues = useCallback(({ valid, values, form }) => {
@@ -103,6 +102,16 @@ export default function Ledger({ className, goHome }) {
103 }
102 }
104 }, []);
103 }, []);
105
104
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+
106 const full = useBreakpoints([true, true, false]);
115 const full = useBreakpoints([true, true, false]);
107 const half = useBreakpoints([false, false, true]);
116 const half = useBreakpoints([false, false, true]);
108 const isHTTPS = document.location.protocol === 'https:';
117 const isHTTPS = document.location.protocol === 'https:';
@@ -162,12 +171,8 @@ export default function Ledger({ className, goHome }) {
162 validate={validate}
171 validate={validate}
163 onValues={onValues}
172 onValues={onValues}
164 onSubmit={onSubmit}
173 onSubmit={onSubmit}
165− initialValues={{
174+ afterSubmit={goHome}
166− useCustomPath: false,
175+ initialValues={initialValues}>
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− }}>
171 {({ handleSubmit }) => (
176 {({ handleSubmit }) => (
172 <>
177 <>
173 <Condition when="useCustomPath" is={true}>
178 <Condition when="useCustomPath" is={true}>
src/views/Login/Mnemonic.js+9−6
@@ -7,16 +7,14 @@ import { useWallet } from 'store/wallet';
7
7
8import { walletFromMnemonic, WALLET_TYPES } from 'lib/wallet';
8import { walletFromMnemonic, WALLET_TYPES } from 'lib/wallet';
9import useLoginView from 'lib/useLoginView';
9import useLoginView from 'lib/useLoginView';
10+import { MnemonicInput, HdPathInput, PassphraseInput } from 'form/Inputs';
10import {
11import {
11− MnemonicInput,
12− HdPathInput,
13− PassphraseInput,
14 composeValidator,
12 composeValidator,
15 buildMnemonicValidator,
13 buildMnemonicValidator,
16 buildCheckboxValidator,
14 buildCheckboxValidator,
17 buildPassphraseValidator,
15 buildPassphraseValidator,
18 buildHdPathValidator,
16 buildHdPathValidator,
19−} from 'form/Inputs';
17+} from 'form/validators';
20import BridgeForm from 'form/BridgeForm';
18import BridgeForm from 'form/BridgeForm';
21import Condition from 'form/Condition';
19import Condition from 'form/Condition';
22import FormError from 'form/FormError';
20import FormError from 'form/FormError';
@@ -60,13 +58,18 @@ export default function Mnemonic({ className, goHome }) {
60 [setAuthMnemonic, setWallet, setWalletHdPath]
58 [setAuthMnemonic, setWallet, setWalletHdPath]
61 );
59 );
62
60
61+ const initialValues = useMemo(
62+ () => ({ hdpath: walletHdPath, useAdvanced: false }),
63+ [walletHdPath]
64+ );
65+
63 return (
66 return (
64 <Grid className={cn('mt4', className)}>
67 <Grid className={cn('mt4', className)}>
65 <BridgeForm
68 <BridgeForm
66 validate={validate}
69 validate={validate}
67 onValues={onValues}
70 onValues={onValues}
68− onSubmit={goHome}
71+ afterSubmit={goHome}
69− initialValues={{ hdpath: walletHdPath, useAdvanced: false }}>
72+ initialValues={initialValues}>
70 {({ handleSubmit }) => (
73 {({ handleSubmit }) => (
71 <>
74 <>
72 <Grid.Item
75 <Grid.Item
src/views/Login/PrivateKey.js+6−3
@@ -6,10 +6,13 @@ import { useWallet } from 'store/wallet';
6
6
7import { EthereumWallet, WALLET_TYPES, stripHexPrefix } from 'lib/wallet';
7import { EthereumWallet, WALLET_TYPES, stripHexPrefix } from 'lib/wallet';
8import useLoginView from 'lib/useLoginView';
8import useLoginView from 'lib/useLoginView';
9+
9import FormError from 'form/FormError';
10import FormError from 'form/FormError';
10−import ContinueButton from './ContinueButton';
11import BridgeForm from 'form/BridgeForm';
11import BridgeForm from 'form/BridgeForm';
12+import { HexInput } from 'form/Inputs';
12−import { buildHexValidator, composeValidator, HexInput } from 'form/Inputs';
13+import { buildHexValidator, composeValidator } from 'form/validators';
14+
15+import ContinueButton from './ContinueButton';
13
16
14export default function PrivateKey({ className, goHome }) {
17export default function PrivateKey({ className, goHome }) {
15 useLoginView(WALLET_TYPES.PRIVATE_KEY);
18 useLoginView(WALLET_TYPES.PRIVATE_KEY);
@@ -39,7 +42,7 @@ export default function PrivateKey({ className, goHome }) {
39
42
40 return (
43 return (
41 <Grid className={className}>
44 <Grid className={className}>
42− <BridgeForm validate={validate} onValues={onValues} onSubmit={goHome}>
45+ <BridgeForm validate={validate} onValues={onValues} afterSubmit={goHome}>
43 {({ handleSubmit }) => (
46 {({ handleSubmit }) => (
44 <>
47 <>
45 <Grid.Item
48 <Grid.Item
src/views/Login/Ticket.js+16−11
@@ -15,20 +15,20 @@ import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
15import useImpliedPoint from 'lib/useImpliedPoint';
15import useImpliedPoint from 'lib/useImpliedPoint';
16import useLoginView from 'lib/useLoginView';
16import useLoginView from 'lib/useLoginView';
17import patp2dec from 'lib/patp2dec';
17import patp2dec from 'lib/patp2dec';
18+import timeout from 'lib/timeout';
19+
18import BridgeForm from 'form/BridgeForm';
20import BridgeForm from 'form/BridgeForm';
19import Condition from 'form/Condition';
21import Condition from 'form/Condition';
22+import { TicketInput, PassphraseInput, PointInput } from 'form/Inputs';
20import {
23import {
21− TicketInput,
22− PassphraseInput,
23− PointInput,
24 composeValidator,
24 composeValidator,
25 buildCheckboxValidator,
25 buildCheckboxValidator,
26 buildTicketValidator,
26 buildTicketValidator,
27 buildPassphraseValidator,
27 buildPassphraseValidator,
28 buildPointValidator,
28 buildPointValidator,
29−} from 'form/Inputs';
29+} from 'form/validators';
30−import timeout from 'lib/timeout';
31import FormError from 'form/FormError';
30import FormError from 'form/FormError';
31+
32import ContinueButton from './ContinueButton';
32import ContinueButton from './ContinueButton';
33
33
34export default function Ticket({ className, goHome }) {
34export default function Ticket({ className, goHome }) {
@@ -135,17 +135,22 @@ export default function Ticket({ className, goHome }) {
135 [setPointCursor, setUrbitWallet]
135 [setPointCursor, setUrbitWallet]
136 );
136 );
137
137
138+ const initialValues = useMemo(
139+ () => ({
140+ point: impliedPoint || '',
141+ usePasshrase: false,
142+ useShards: false,
143+ }),
144+ [impliedPoint]
145+ );
146+
138 return (
147 return (
139 <Grid className={cn('mt4', className)}>
148 <Grid className={cn('mt4', className)}>
140 <BridgeForm
149 <BridgeForm
141 validate={validate}
150 validate={validate}
142 onValues={onValues}
151 onValues={onValues}
143− onSubmit={goHome}
152+ afterSubmit={goHome}
144− initialValues={{
153+ initialValues={initialValues}>
145− point: impliedPoint || '',
146− usePasshrase: false,
147− useShards: false,
148− }}>
149 {({ handleSubmit }) => (
154 {({ handleSubmit }) => (
150 <>
155 <>
151 <Grid.Item full as={PointInput} name="point" />
156 <Grid.Item full as={PointInput} name="point" />
src/views/Login/Trezor.js+17−8
@@ -24,19 +24,21 @@ import {
24 buildCheckboxValidator,
24 buildCheckboxValidator,
25 buildSelectValidator,
25 buildSelectValidator,
26 buildHdPathValidator,
26 buildHdPathValidator,
27−} from 'form/Inputs';
27+} from 'form/validators';
28import BridgeForm from 'form/BridgeForm';
28import BridgeForm from 'form/BridgeForm';
29import Condition from 'form/Condition';
29import Condition from 'form/Condition';
30−import ContinueButton from './ContinueButton';
31import FormError from 'form/FormError';
30import FormError from 'form/FormError';
32
31
32+import ContinueButton from './ContinueButton';
33+import { FORM_ERROR } from 'final-form';
34+
33const ACCOUNT_OPTIONS = times(20, i => ({
35const ACCOUNT_OPTIONS = times(20, i => ({
34 text: `Account #${i + 1}`,
36 text: `Account #${i + 1}`,
35 value: i,
37 value: i,
36}));
38}));
37
39
38// see Ledger.js for context — Trezor is basicaly Ledger with less complexity
40// 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 }) {
40 useLoginView(WALLET_TYPES.TREZOR);
42 useLoginView(WALLET_TYPES.TREZOR);
41
43
42 const { setWallet, setWalletHdPath } = useWallet();
44 const { setWallet, setWalletHdPath } = useWallet();
@@ -72,6 +74,7 @@ export default function Trezor({ className }) {
72 setWalletHdPath(values.hdPath);
74 setWalletHdPath(values.hdPath);
73 } else {
75 } else {
74 setWallet(Nothing());
76 setWallet(Nothing());
77+ return { [FORM_ERROR]: 'Failed to authenticate with your Trezor.' };
75 }
78 }
76 },
79 },
77 [setWallet, setWalletHdPath]
80 [setWallet, setWalletHdPath]
@@ -87,6 +90,15 @@ export default function Trezor({ className }) {
87 }
90 }
88 }, []);
91 }, []);
89
92
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+
90 return (
102 return (
91 <Grid className={className}>
103 <Grid className={className}>
92 <Grid.Item full as={H5}>
104 <Grid.Item full as={H5}>
@@ -102,11 +114,8 @@ export default function Trezor({ className }) {
102 validate={validate}
114 validate={validate}
103 onValues={onValues}
115 onValues={onValues}
104 onSubmit={onSubmit}
116 onSubmit={onSubmit}
105− initialValues={{
117+ afterSubmit={goHome}
106− useCustomPath: false,
118+ initialValues={initialValues}>
107− hdpath: TREZOR_PATH.replace(/x/g, ACCOUNT_OPTIONS[0].value),
108− account: ACCOUNT_OPTIONS[0].value,
109− }}>
110 {({ handleSubmit }) => (
119 {({ handleSubmit }) => (
111 <>
120 <>
112 <Condition when="useCustomPath" is={true}>
121 <Condition when="useCustomPath" is={true}>
src/views/Party/PartySetPoolSize.js+8−7
@@ -11,21 +11,21 @@ import * as need from 'lib/need';
11import useEthereumTransaction from 'lib/useEthereumTransaction';
11import useEthereumTransaction from 'lib/useEthereumTransaction';
12import { GAS_LIMITS } from 'lib/constants';
12import { GAS_LIMITS } from 'lib/constants';
13import { useLocalRouter } from 'lib/LocalRouter';
13import { useLocalRouter } from 'lib/LocalRouter';
14+import useCurrentPermissions from 'lib/useCurrentPermissions';
15+import patp2dec from 'lib/patp2dec';
14
16
15import ViewHeader from 'components/ViewHeader';
17import ViewHeader from 'components/ViewHeader';
16import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
17−import patp2dec from 'lib/patp2dec';
18import View from 'components/View';
19import View from 'components/View';
19−import useCurrentPermissions from 'lib/useCurrentPermissions';
20import WarningBox from 'components/WarningBox';
20import WarningBox from 'components/WarningBox';
21+
21import BridgeForm from 'form/BridgeForm';
22import BridgeForm from 'form/BridgeForm';
23+import { PointInput, NumberInput } from 'form/Inputs';
22import {
24import {
23− PointInput,
24 composeValidator,
25 composeValidator,
25− NumberInput,
26 buildNumberValidator,
26 buildNumberValidator,
27 buildPointValidator,
27 buildPointValidator,
28−} from 'form/Inputs';
28+} from 'form/validators';
29import FormError from 'form/FormError';
29import FormError from 'form/FormError';
30
30
31function useSetPoolSize() {
31function useSetPoolSize() {
@@ -88,6 +88,8 @@ export default function PartySetPoolSize() {
88 [construct, unconstruct]
88 [construct, unconstruct]
89 );
89 );
90
90
91+ const initialValues = useMemo(() => ({ poolSize: 5 }), []);
92+
91 return (
93 return (
92 <View pop={pop} inset>
94 <View pop={pop} inset>
93 <Grid>
95 <Grid>
@@ -107,9 +109,8 @@ export default function PartySetPoolSize() {
107
109
108 <BridgeForm
110 <BridgeForm
109 validate={validate}
111 validate={validate}
110− onSubmit={() => {}}
111 onValues={onValues}
112 onValues={onValues}
112− initialValues={{ poolSize: 5 }}>
113+ initialValues={initialValues}>
113 {({ handleSubmit, values }) => (
114 {({ handleSubmit, values }) => (
114 <>
115 <>
115 <Grid.Item
116 <Grid.Item