on master help sign in
fix dirtySinceLastSubmit for email chip input
commitc19b2a84292649a60d1e21d61a46ca99569bdc0a authorLiam Fitzgerald <liam@urizen.co> dateWed 8 Jan 2020, 20:55 (7 years ago) parenta0107e0
5 files changed+129−74
src/form/BridgeForm.js+1 src/form/EmailChipInput.js+19−36 src/form/FormError.js+1 src/lib/inviteMail.js+2 src/views/Invite/Cohort.js+106−38
src/form/BridgeForm.js+1
@@ -25,6 +25,7 @@ export default function BridgeForm({
2525 async (...args) => {
2626 const errors = await onSubmit(...args);
2727 if (errors) {
28+ console.log(errors);
2829 return errors;
2930 }
3031
src/form/EmailChipInput.js+19−36
@@ -1,19 +1,20 @@
11import React, { useState, useCallback } from 'react';
2−import { isEqual } from 'lodash';
32import { Flex, ErrorText } from 'indigo-react';
43import cn from 'classnames';
54import { useField } from 'react-final-form';
5+import { useFieldArray } from 'react-final-form-arrays';
66
77import { hasErrors } from 'form/validators';
88
9−const Chip = ({ value, onDelete, disabled }) => {
9+const Chip = ({ onDelete, disabled, name }) => {
10+ const { input } = useField(name, { subscription: { value: true } });
1011 return (
1112 <Flex
1213 className={cn('r32 ph2 m1 flex-center h7', {
1314 'bg-gray2': !disabled,
1415 'bg-gray1 gray4': disabled,
1516 })}>
16− <Flex.Item>{value}</Flex.Item>
17+ <Flex.Item>{input.value}</Flex.Item>
1718 <Flex.Item onClick={disabled ? onDelete : undefined} className="ml1 f6">
1819 ✗
1920 </Flex.Item>
@@ -22,42 +23,25 @@ const Chip = ({ value, onDelete, disabled }) => {
2223};
2324
2425const EmailChipInput = ({ className, name, label, disabled }) => {
25− const [chips, _setChips] = useState([]);
26+ const {
27+ fields,
28+ meta: { error, active },
29+ } = useFieldArray(name, { subscription: { error: true, active: true } });
2630
2731 const [value, setValue] = useState('');
2832
2933 const handleChange = useCallback(
3034 event => {
35+ event.preventDefault();
3136 setValue(event.target.value);
3237 },
3338 [setValue]
3439 );
3540
36− const {
37− input,
38− meta: { active, error },
39− } = useField(name, {
40− subscription: {
41− active: true,
42− error: true,
43− value: true,
44− },
45− isEqual,
46− });
47−
48− const setChips = useCallback(
49− chips => {
50− console.log(chips);
51− _setChips(chips);
52− input.onChange(chips);
53− },
54− [input, _setChips]
55− );
56−
5741 const addToChips = useCallback(() => {
58− setChips([...chips, value]);
42+ fields.push(value);
5943 setValue('');
60− }, [chips, value, setChips]);
44+ }, [value, fields, setValue]);
6145
6246 const handleKeyDown = useCallback(
6347 event => {
@@ -66,29 +50,29 @@ const EmailChipInput = ({ className, name, label, disabled }) => {
6650 addToChips();
6751 } else if ('Backspace' === event.key && value === '') {
6852 event.preventDefault();
69− setChips(chips.slice(0, -1));
53+ fields.pop();
7054 } else if (disabled) {
7155 event.preventDefault();
7256 }
7357 },
74− [setChips, chips, value, addToChips, disabled]
58+ [fields, value, disabled, addToChips]
7559 );
7660
7761 const handleDelete = useCallback(
7862 idx => () => {
79− setChips([...chips.slice(0, idx), ...chips.slice(idx + 1)]);
63+ fields.remove(idx);
8064 },
81− [setChips, chips]
65+ [fields]
8266 );
8367
8468 const handleBlur = useCallback(
8569 e => {
70+ e.preventDefault();
8671 if (value !== '') {
8772 addToChips();
88− input.onBlur();
8973 }
9074 },
91− [addToChips, value, input]
75+ [addToChips, value]
9276 );
9377
9478 const hasError = hasErrors(error);
@@ -105,10 +89,10 @@ const EmailChipInput = ({ className, name, label, disabled }) => {
10589 'b-green2': active && !disabled && !hasError,
10690 'b-red3': active && !disabled && hasError,
10791 })}>
108− {chips.map((chip, idx) => (
92+ {fields.map((name, idx) => (
10993 <Chip
11094 key={idx}
111− value={chip}
95+ name={name}
11296 disabled={disabled}
11397 onDelete={handleDelete(idx)}
11498 />
@@ -119,7 +103,6 @@ const EmailChipInput = ({ className, name, label, disabled }) => {
119103 className="mv1 mh2 h7 b-none flex-grow"
120104 placeholder="Enter an email address"
121105 value={value}
122− name={name}
123106 onChange={handleChange}
124107 onKeyDown={handleKeyDown}
125108 onBlur={handleBlur}
src/form/FormError.js+1
@@ -25,6 +25,7 @@ export default function FormError(props) {
2525
2626 const warning = submitErrors && submitErrors[WARNING];
2727 const showWarning = !!warning && !dirtySinceLastSubmit;
28+ console.log(dirtySinceLastSubmit);
2829
2930 return showSubmitError || showValidationError || showWarning ? (
3031 <ErrorText {...props}>
src/lib/inviteMail.js+2
@@ -37,6 +37,8 @@ function sendMail(recipient, ticket, sender, message, tx) {
3737 sender,
3838 message,
3939 tx,
40+ }).then(() => {
41+ throw new Error('test');
4042 });
4143}
4244
src/views/Invite/Cohort.js+106−38
@@ -1,15 +1,14 @@
11import React, { useMemo, useCallback, useState, useEffect } from 'react';
22import { Grid, ErrorText } from 'indigo-react';
3−import { get } from 'lodash';
3+import { get, uniq, zip, compact, isEqual } from 'lodash';
44import * as azimuth from 'azimuth-js';
55import { FORM_ERROR } from 'final-form';
66import { toWei, toBN } from 'web3-utils';
7+import * as ob from 'urbit-ob';
78
89import * as need from 'lib/need';
910import * as wg from 'lib/walletgen';
1011import * as tank from 'lib/tank';
11−// import useMailer from 'lib/useMailer';
12−import timeout from 'lib/timeout';
1312import useCopiable from 'lib/useCopiable';
1413
1514import Tabs from 'components/Tabs';
@@ -22,7 +21,6 @@ import { useWallet } from 'store/wallet';
2221import { usePointCursor } from 'store/pointCursor';
2322import { usePointCache } from 'store/pointCache';
2423
25−// import { } from ''
2624import {
2725 signTransaction,
2826 sendSignedTransaction,
@@ -38,11 +36,13 @@ import FormError from 'form/FormError';
3836import SubmitButton from 'form/SubmitButton';
3937import BridgeForm from 'form/BridgeForm';
4038import {
41− buildEmailArrayValidator,
4239 composeValidator,
4340 hasErrors,
41+ buildEmailValidator,
42+ buildArrayValidator,
4443} from 'form/validators';
4544import { WARNING } from 'form/helpers';
45+import useMailer from 'lib/useMailer';
4646
4747const GAS_LIMIT = GAS_LIMITS.GIFT_PLANET;
4848
@@ -53,6 +53,8 @@ const STATUS = {
5353 FAILURE: 'FAILURE',
5454};
5555
56+const HAS_RECEIVED_TEXT = 'This email has already received an invite.';
57+
5658const useInviter = sendInvites => {
5759 const { contracts, web3, networkType } = useNetwork();
5860 const { wallet, walletType, walletHdPath } = useWallet();
@@ -80,6 +82,7 @@ const useInviter = sendInvites => {
8082 setInvites([]);
8183 setTxStatus(STATUS.INPUT);
8284 }, [setProgress, setInvites, setTxStatus]);
85+
8386 const generateInvites = useCallback(
8487 async numInvites => {
8588 const _contracts = contracts.getOrElse(null);
@@ -102,7 +105,6 @@ const useInviter = sendInvites => {
102105 setTxStatus(STATUS.SENDING);
103106 // account for the race condition where invites got used up while we were
104107 // composing our target list
105− console.log('a');
106108 if (planets.length < numInvites) {
107109 // resync invites to the cache, since they're out of date
108110 syncInvites(point);
@@ -110,9 +112,11 @@ const useInviter = sendInvites => {
110112 setTxStatus(STATUS.FAILURE);
111113
112114 return {
113− [FORM_ERROR]:
114− `Can currently only send ${planets.length} invites. ` +
115− `Please remove invites until you are within the limit.`,
115+ errors: {
116+ [FORM_ERROR]:
117+ `Can currently only send ${planets.length} invites. ` +
118+ `Please remove invites until you are within the limit.`,
119+ },
116120 };
117121 }
118122
@@ -155,7 +159,9 @@ const useInviter = sendInvites => {
155159 } catch (error) {
156160 console.error(error);
157161 return {
158− [WARNING]: `There was an error while generating wallets.`,
162+ errors: {
163+ [WARNING]: `There was an error while generating wallets.`,
164+ },
159165 };
160166 }
161167 }
@@ -201,17 +207,24 @@ const useInviter = sendInvites => {
201207 console.log(confirmedInvites);
202208
203209 if (unsentInvites.length > 0) {
204− return { [FORM_ERROR]: unsentInvites };
210+ return { errors: { [FORM_ERROR]: unsentInvites } };
205211 }
206212
207213 if (errorCount > 0) {
208214 return {
209− [WARNING]: `There ${pluralize(errorCount, 'was', 'were')} ${pluralize(
210− errorCount,
211− 'error'
212− )} while generating wallets. You can still send the invites that generated correctly.`,
215+ errors: {
216+ [WARNING]: `There ${pluralize(
217+ errorCount,
218+ 'was',
219+ 'were'
220+ )} ${pluralize(
221+ errorCount,
222+ 'error'
223+ )} while generating wallets. You can still send the invites that generated correctly.`,
224+ },
213225 };
214226 }
227+ return { invites: confirmedInvites };
215228 },
216229 [
217230 contracts,
@@ -238,14 +251,9 @@ const useInviter = sendInvites => {
238251};
239252
240253const InviteMail = () => {
241− const {
242− progress,
243− txStatus,
244− needFunds,
245− invites,
246− generateInvites,
247− resetInvites,
248− } = useInviter();
254+ const { progress, txStatus, needFunds, generateInvites } = useInviter();
255+
256+ const { sendMail, getHasReceived } = useMailer();
249257
250258 const [status, setStatus] = useState(STATUS.INPUT);
251259
@@ -253,31 +261,62 @@ const InviteMail = () => {
253261 const canInput = status === STATUS.INPUT;
254262 const isSending = txStatus === STATUS.SENDING || status === STATUS.SENDING;
255263 const isDone = status === STATUS.SUCCESS;
264+ const isFailed = status === STATUS.FAILED;
265+
266+ const { pointCursor } = usePointCursor();
267+ const point = need.point(pointCursor);
268+
269+ const sendInvites = useCallback(
270+ async (emails, invites) => {
271+ const mailingErrors = compact(
272+ await Promise.all(
273+ zip(emails, invites).map(([email, invite]) =>
274+ sendMail(email, invite.ticket, ob.patp(point), '', invite.rawTx)
275+ .then(() => null)
276+ .catch(
277+ () => `Sending ticket ${invite.ticket} to ${email} failed.`
278+ )
279+ )
280+ )
281+ ).join(', ');
282+ if (mailingErrors.length) {
283+ return { [FORM_ERROR]: mailingErrors };
284+ }
285+ },
286+ [point, sendMail]
287+ );
288+
256289 const onSubmit = useCallback(
257290 async values => {
258291 const emailCount = values.emails.length;
259292 setCount(emailCount);
260293 setStatus(STATUS.SENDING);
261− const errors = await generateInvites(emailCount);
294+ const { errors, invites } = await generateInvites(emailCount);
262295 if (errors) {
263296 setStatus(STATUS.FAILED);
264297 return errors;
265298 }
299+ const mailErrors = await sendInvites(values.emails, invites);
300+ console.log(mailErrors);
301+ if (mailErrors) {
302+ console.log(mailErrors);
303+ setStatus(STATUS.FAILED);
304+ return mailErrors;
305+ }
306+ setStatus(STATUS.SUCCESS);
266307 },
267− [generateInvites, setCount, setStatus]
308+ [generateInvites, setCount, setStatus, sendInvites]
268309 );
269310
270− useEffect(() => {
271− async function processInvites() {
272− if (invites.length !== 0 && txStatus === STATUS.SUCCESS) {
273− await timeout(500);
274−
275− resetInvites();
276− setStatus(STATUS.SUCCESS);
311+ const validateHasReceived = useCallback(
312+ async email => {
313+ const hasReceived = await getHasReceived(email);
314+ if (hasReceived) {
315+ return HAS_RECEIVED_TEXT;
277316 }
278− }
279− processInvites();
280− }, [invites, setStatus, status, txStatus, resetInvites]);
317+ },
318+ [getHasReceived]
319+ );
281320
282321 const buttonText = () => {
283322 switch (status) {
@@ -324,22 +363,51 @@ const InviteMail = () => {
324363 </Grid.Item>
325364 );
326365 }
366+ if (isFailed) {
367+ return (
368+ <Grid.Item full className="mt4 p3 bg-red3 white f5">
369+ {buttonText(count)}
370+ </Grid.Item>
371+ );
372+ }
327373
328374 return null;
329375 };
330376
377+ const validateForm = useCallback((values, errors) => {
378+ if (hasErrors(errors)) {
379+ return errors;
380+ }
381+
382+ // check for email uniqenesss
383+ const emails = values.emails.filter(d => !!d);
384+ if (uniq(emails).length !== emails.length) {
385+ return { [FORM_ERROR]: 'Duplicate email.' };
386+ }
387+ }, []);
388+
331389 const validate = useMemo(
332− () => composeValidator({ emails: buildEmailArrayValidator() }),
333− []
390+ () =>
391+ composeValidator(
392+ {
393+ emails: buildArrayValidator(buildEmailValidator(validateHasReceived)),
394+ },
395+ validateForm
396+ ),
397+ [validateHasReceived, validateForm]
334398 );
399+
400+ const initialValues = { emails: [] };
335401 return (
336402 <Grid.Item full as={Grid}>
337403 <BridgeForm
338404 validate={validate}
339− initialValues={{ emails: [] }}
405+ initialValues={initialValues}
406+ initialValuesEqual={isEqual}
340407 onSubmit={onSubmit}>
341408 {({ handleSubmit, values }) => (
342409 <>
410+ {console.log(values) && null}
343411 <Grid.Item full as={EmailChipInput} name="emails" className="mt4" />
344412 {renderButton(handleSubmit)}
345413