on master help sign in
feat: put additional config updates through useForm
5c991afMatt Condon (shrugs)
src/indigo-react/lib/useForm.js+31−24
@@ -111,47 +111,54 @@ export default function useForm(configs = []) {
111 () =>
111 () =>
112 configs.map(
112 configs.map(
113 (config, i) =>
113 (config, i) =>
114+ config.error ||
114− !validations[i].pass &&
115+ (!validations[i].pass &&
115− hasBeenFocused[config.name] &&
116+ hasBeenFocused[config.name] &&
116− !focused[config.name] &&
117+ !focused[config.name] &&
117− validations[i].error
118+ validations[i].error)
118 ),
119 ),
119 [configs, validations, hasBeenFocused, focused]
120 [configs, validations, hasBeenFocused, focused]
120 );
121 );
121
122
122 // visibly tell the user that their input has passed if
123 // visibly tell the user that their input has passed if
123 // 1) it has passed
124 // 1) it has passed
125+ // 2) there are no errors
124− // 2) they are or have interacted with the input before
126+ // 3) they are or have interacted with the input before
125 const visiblePasses = useMemo(
127 const visiblePasses = useMemo(
126 () =>
128 () =>
127 configs.map(
129 configs.map(
128 (config, i) =>
130 (config, i) =>
129 validations[i].pass &&
131 validations[i].pass &&
132+ !errors[i] &&
130 (hasBeenFocused[config.name] || focused[config.name])
133 (hasBeenFocused[config.name] || focused[config.name])
131 ),
134 ),
132− [configs, validations, hasBeenFocused, focused]
135+ [configs, validations, errors, hasBeenFocused, focused]
133 );
136 );
134
137
135 const inputs = useMemo(
138 const inputs = useMemo(
136 () =>
139 () =>
137− configs.map((config, i) => ({
140+ configs.map(
141+ ({ name, error, autoFocus, disabled, initialValue, ...rest }, i) => ({
138− // Input props
142+ // Input props
143+ name,
139− data: datas[i],
144+ data: datas[i],
140− pass: passes[i],
145+ pass: passes[i],
141− visiblyPassed: visiblePasses[i],
146+ visiblyPassed: visiblePasses[i],
142− error: errors[i],
147+ error: errors[i],
143− focused: focuses[i],
148+ focused: focuses[i],
144− ...config,
145− autoFocus: config.autoFocus && !config.disabled,
149+ autoFocus: autoFocus && !disabled,
150+ disabled,
151+ ...rest,
146− // dom properties below:
152+ // dom properties below:
147− bind: {
153+ bind: {
148− value: values[config.name] || config.initialValue,
154+ value: values[name] || initialValue,
149− onChange: onChange(config.name),
155+ onChange: onChange(name),
150− onFocus: onFocus(config.name),
156+ onFocus: onFocus(name),
151− onBlur: onBlur(config.name),
157+ onBlur: onBlur(name),
152− autoFocus: config.autoFocus && !config.disabled,
158+ autoFocus: autoFocus && !disabled,
153− },
159+ },
154− })),
160+ })
161+ ),
155 [
162 [
156 configs,
163 configs,
157 datas,
164 datas,
src/views/Invite/InviteEmail.js+34−23
@@ -1,6 +1,6 @@
1−import React, { useCallback, useEffect, useState } from 'react';
1+import React, { useCallback, useEffect, useMemo, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3−import Maybe, { Just, Nothing } from 'folktale/maybe';
3+import Maybe from 'folktale/maybe';
4import * as azimuth from 'azimuth-js';
4import * as azimuth from 'azimuth-js';
5import { Grid, Flex, Input, IconButton, HelpText, Text } from 'indigo-react';
5import { Grid, Flex, Input, IconButton, HelpText, Text } from 'indigo-react';
6import { uniq } from 'lodash';
6import { uniq } from 'lodash';
@@ -90,7 +90,6 @@ const buildAccessoryFor = (dones, errors) => name => (
90);
90);
91
91
92// TODO: refactor transactions + mailer into hooks
92// TODO: refactor transactions + mailer into hooks
93−// TODO: include input-specific error info in form elements
94// TODO: put accessory inside of input
93// TODO: put accessory inside of input
95// TODO: implement hasReceived cache with useHasReceived
94// TODO: implement hasReceived cache with useHasReceived
96export default function NewInviteEmail() {
95export default function NewInviteEmail() {
@@ -116,13 +115,7 @@ export default function NewInviteEmail() {
116 buildInputConfig
115 buildInputConfig
117 );
116 );
118
117
119− const [hovered, _setHovered] = useState({});
118+ const [hovered, setHovered] = useSetState({});
120− const setHovered = (name, value) => () =>
121− _setHovered((value = { ...value, [name]: value }));
122−
123− // TODO: pass disabled, error info into the useForm hook?
124− const { inputs, pass } = useForm(inputConfigs);
125−
126 const [invites, addInvite, clearInvites] = useSetState({});
119 const [invites, addInvite, clearInvites] = useSetState({});
127 const [receipts, addReceipt, clearReceipts] = useSetState({});
120 const [receipts, addReceipt, clearReceipts] = useSetState({});
128 const [errors, addError, clearError] = useSetState({});
121 const [errors, addError, clearError] = useSetState({});
@@ -131,17 +124,29 @@ export default function NewInviteEmail() {
131 const [needFunds, setNeedFunds] = useState(null);
124 const [needFunds, setNeedFunds] = useState(null);
132 const [generalError, setGeneralError] = useState(null);
125 const [generalError, setGeneralError] = useState(null);
133
126
134− const allPass = pass && !generalError;
135−
136 const canInput = status === STATUS.INPUT;
127 const canInput = status === STATUS.INPUT;
137− const canGenerate = allPass && status === STATUS.INPUT;
138 const canSend = status === STATUS.CAN_SEND;
128 const canSend = status === STATUS.CAN_SEND;
139 const isGenerating = status === STATUS.GENERATING;
129 const isGenerating = status === STATUS.GENERATING;
140 const isSending = status === STATUS.SENDING;
130 const isSending = status === STATUS.SENDING;
141 const isFunding = status === STATUS.FUNDING;
131 const isFunding = status === STATUS.FUNDING;
142 const isFailed = status === STATUS.FAILURE;
132 const isFailed = status === STATUS.FAILURE;
143 const isDone = status === STATUS.SUCCESS;
133 const isDone = status === STATUS.SUCCESS;
134+
135+ const dynamicConfigs = useMemo(
136+ () =>
137+ inputConfigs.map(config => {
138+ config.disabled = !canInput;
139+ console.log(config.name, errors[config.name]);
140+ config.error = errors[config.name];
141+ return config;
142+ }),
143+ [inputConfigs, errors, canInput]
144+ );
145+ const { inputs, pass } = useForm(dynamicConfigs);
146+
144 const canAddInvite = canInput && inputs.length < maxInvitesToSend;
147 const canAddInvite = canInput && inputs.length < maxInvitesToSend;
148+ const allPass = pass && !generalError;
149+ const canGenerate = allPass && status === STATUS.INPUT;
145
150
146 const progress = isGenerating
151 const progress = isGenerating
147 ? Object.keys(invites).length
152 ? Object.keys(invites).length
@@ -211,7 +216,7 @@ export default function NewInviteEmail() {
211 walletHdPath,
216 walletHdPath,
212 networkType,
217 networkType,
213 // TODO: ^ make a useTransactionSigner to encapsulate this logic
218 // TODO: ^ make a useTransactionSigner to encapsulate this logic
214− txn: Just(inviteTx),
219+ txn: Maybe.Just(inviteTx),
215 gasPrice: GAS_PRICE_GWEI.toString(),
220 gasPrice: GAS_PRICE_GWEI.toString(),
216 gasLimit: GAS_LIMIT.toString(),
221 gasLimit: GAS_LIMIT.toString(),
217 nonce: nonce + i,
222 nonce: nonce + i,
@@ -230,7 +235,10 @@ export default function NewInviteEmail() {
230
235
231 if (errorCount > 0) {
236 if (errorCount > 0) {
232 throw new Error(
237 throw new Error(
238+ `There ${pluralize(errorCount, 'was', 'were')} ${pluralize(
239+ errorCount,
240+ 'error'
233− `There were ${errorCount} errors while generating wallets.`
241+ )} while generating wallets.`
234 );
242 );
235 }
243 }
236 }, [
244 }, [
@@ -276,7 +284,7 @@ export default function NewInviteEmail() {
276 try {
284 try {
277 const txHash = await sendSignedTransaction(
285 const txHash = await sendSignedTransaction(
278 _web3,
286 _web3,
279− Just(invite.signedTx),
287+ Maybe.Just(invite.signedTx),
280 tankWasUsed,
288 tankWasUsed,
281 () => {}
289 () => {}
282 );
290 );
@@ -288,7 +296,7 @@ export default function NewInviteEmail() {
288 console.error(error);
296 console.error(error);
289 errorCount++;
297 errorCount++;
290 addError({
298 addError({
291− [input.name]: `Transaction Failure for ${invite.email}}`,
299+ [input.name]: `Transaction Failure for ${invite.email}`,
292 });
300 });
293 }
301 }
294
302
@@ -308,7 +316,7 @@ export default function NewInviteEmail() {
308 console.error(error);
316 console.error(error);
309 errorCount++;
317 errorCount++;
310 addError({
318 addError({
311− [input.name]: `Mailing Failure for ${invite.email}}`,
319+ [input.name]: `Mailing Failure for ${invite.email}`,
312 });
320 });
313 }
321 }
314
322
@@ -319,7 +327,10 @@ export default function NewInviteEmail() {
319 // if there are any receipt errors, throw a general error
327 // if there are any receipt errors, throw a general error
320 if (errorCount > 0) {
328 if (errorCount > 0) {
321 throw new Error(
329 throw new Error(
330+ `There ${pluralize(errorCount, 'was', 'were')} ${pluralize(
331+ errorCount,
332+ 'error'
322− `There were ${errorCount} errors while sending transactions.`
333+ )} while sending transactions.`
323 );
334 );
324 }
335 }
325 }, [
336 }, [
@@ -368,7 +379,8 @@ export default function NewInviteEmail() {
368 }, [canGenerate, canSend, onClickGenerate, onClickSend]);
379 }, [canGenerate, canSend, onClickGenerate, onClickSend]);
369
380
370 useEffect(() => {
381 useEffect(() => {
371− if (uniq(inputs.map(i => i.data)).length !== inputs.length) {
382+ const emails = inputs.map(i => i.data).filter(d => !!d);
383+ if (uniq(emails).length !== emails.length) {
372 setGeneralError(new Error(`Duplicate email.`));
384 setGeneralError(new Error(`Duplicate email.`));
373 } else {
385 } else {
374 setGeneralError(null);
386 setGeneralError(null);
@@ -418,15 +430,14 @@ export default function NewInviteEmail() {
418 key={input.name}
430 key={input.name}
419 as={Grid}
431 as={Grid}
420 gap={12}
432 gap={12}
421− onMouseEnter={setHovered(input.name, true)}
433+ onMouseOver={() => setHovered({ [input.name]: true })}
422− onMouseLeave={setHovered(input.name, false)}
434+ onMouseLeave={() => setHovered({ [input.name]: false })}
423 full>
435 full>
424 <Grid.Item
436 <Grid.Item
425 as={Input}
437 as={Input}
426 cols={[1, 11]}
438 cols={[1, 11]}
427 {...input}
439 {...input}
428 accessory={accessoryFor(input.name)}
440 accessory={accessoryFor(input.name)}
429− disabled={!canInput}
430 />
441 />
431 {!isFirst &&
442 {!isFirst &&
432 (input.focused || hovered[input.name]) &&
443 (input.focused || hovered[input.name]) &&
src/views/Point.js+2−2
@@ -48,9 +48,9 @@ export default function Point() {
48 Boot Arvo
48 Boot Arvo
49 </ForwardButton>
49 </ForwardButton>
50 </Grid.Item>
50 </Grid.Item>
51− <Grid.Item full>
51+ {/* <Grid.Item full>
52 <Actions />
52 <Actions />
53− </Grid.Item>
53+ </Grid.Item> */}
54 </Grid>
54 </Grid>
55
55
56 <FooterButton onClick={goInvite}>
56 <FooterButton onClick={goInvite}>