on master help sign in
feat: abstract basic mailer logic
b465a33Matt Condon (shrugs)
src/lib/useMailer.jsnew file+44
@@ -0,0 +1,44 @@
1+import { useCallback, useEffect } from 'react';
2+import Maybe from 'folktale/maybe';
3+
4+import { hasReceived, sendMail } from './inviteMail';
5+import useSetState from './useSetState';
6+
7+function useHasReceivedCache(emails = []) {
8+ const [cache, addToCache] = useSetState();
9+
10+ const getHasRecieved = useCallback(
11+ email => cache[email] || Maybe.Nothing(), //
12+ [cache]
13+ );
14+
15+ useEffect(() => {
16+ for (const email of emails) {
17+ if (Maybe.Just.hasInstance(getHasRecieved(email))) {
18+ // never update the cache after we know about it
19+ return;
20+ }
21+
22+ (async () => {
23+ addToCache({ [email]: await hasReceived(email) });
24+ })();
25+ }
26+ }, [emails, getHasRecieved, addToCache]);
27+
28+ return { getHasRecieved };
29+}
30+
31+export default function useMailer(emails) {
32+ const { getHasRecieved } = useHasReceivedCache(emails);
33+
34+ // prefix to avoid clobbering sendMail import
35+ const _sendMail = useCallback(async (email, ticket, rawTx) => {
36+ const mailSuccess = await sendMail(email, ticket, rawTx);
37+
38+ if (!mailSuccess) {
39+ throw new Error(`Internal mailing error when mailing ${email}`);
40+ }
41+ }, []);
42+
43+ return { getHasRecieved, sendMail: _sendMail };
44+}
src/views/Invite/InviteEmail.js+13−11
@@ -6,7 +6,6 @@ import { Grid, Flex, Input, IconButton, HelpText, Text } from 'indigo-react';
6import { uniq } from 'lodash';
6import { uniq } from 'lodash';
7
7
8import * as need from 'lib/need';
8import * as need from 'lib/need';
9−import { hasReceived, sendMail } from 'lib/inviteMail';
10import * as wg from 'lib/walletgen';
9import * as wg from 'lib/walletgen';
11
10
12// for transaction generation and signing
11// for transaction generation and signing
@@ -35,6 +34,7 @@ import { usePointCache } from 'store/pointCache';
35import { useWallet } from 'store/wallet';
34import { useWallet } from 'store/wallet';
36import useSetState from 'lib/useSetState';
35import useSetState from 'lib/useSetState';
37import pluralize from 'lib/pluralize';
36import pluralize from 'lib/pluralize';
37+import useMailer from 'lib/useMailer';
38
38
39const kStubMailer = process.env.REACT_APP_STUB_MAILER === 'true';
39const kStubMailer = process.env.REACT_APP_STUB_MAILER === 'true';
40
40
@@ -89,10 +89,10 @@ const buildAccessoryFor = (dones, errors) => name => (
89 </Flex>
89 </Flex>
90);
90);
91
91
92−// TODO: refactor transactions + mailer into hooks
92+// TODO: how to handle dependency loop between inputConfigs and hasReceived?
93+// TODO: test with tank, successful txs
93// TODO: put accessory inside of input
94// TODO: put accessory inside of input
94−// TODO: implement hasReceived cache with useHasReceived
95−export default function NewInviteEmail() {
95+export default function InviteEmail() {
96 // TODO: resumption after error?
96 // TODO: resumption after error?
97 const { pop } = useLocalRouter();
97 const { pop } = useLocalRouter();
98 const { contracts, web3, networkType } = useNetwork();
98 const { contracts, web3, networkType } = useNetwork();
@@ -115,10 +115,10 @@ export default function NewInviteEmail() {
115 buildInputConfig
115 buildInputConfig
116 );
116 );
117
117
118− const [hovered, setHovered] = useSetState({});
118+ const [hovered, setHovered] = useSetState();
119− const [invites, addInvite, clearInvites] = useSetState({});
119+ const [invites, addInvite, clearInvites] = useSetState();
120− const [receipts, addReceipt, clearReceipts] = useSetState({});
120+ const [receipts, addReceipt, clearReceipts] = useSetState();
121− const [errors, addError, clearError] = useSetState({});
121+ const [errors, addError, clearError] = useSetState();
122
122
123 const [status, setStatus] = useState(STATUS.INPUT);
123 const [status, setStatus] = useState(STATUS.INPUT);
124 const [needFunds, setNeedFunds] = useState(null);
124 const [needFunds, setNeedFunds] = useState(null);
@@ -136,13 +136,16 @@ export default function NewInviteEmail() {
136 () =>
136 () =>
137 inputConfigs.map(config => {
137 inputConfigs.map(config => {
138 config.disabled = !canInput;
138 config.disabled = !canInput;
139− console.log(config.name, errors[config.name]);
140 config.error = errors[config.name];
139 config.error = errors[config.name];
141 return config;
140 return config;
142 }),
141 }),
143 [inputConfigs, errors, canInput]
142 [inputConfigs, errors, canInput]
144 );
143 );
145 const { inputs, pass } = useForm(dynamicConfigs);
144 const { inputs, pass } = useForm(dynamicConfigs);
145+ const emails = useMemo(() => inputs.map(i => i.data).filter(d => !!d), [
146+ inputs,
147+ ]);
148+ const { getHasRecieved, sendMail } = useMailer(emails);
146
149
147 const canAddInvite = canInput && inputs.length < maxInvitesToSend;
150 const canAddInvite = canInput && inputs.length < maxInvitesToSend;
148 const allPass = pass && !generalError;
151 const allPass = pass && !generalError;
@@ -379,13 +382,12 @@ export default function NewInviteEmail() {
379 }, [canGenerate, canSend, onClickGenerate, onClickSend]);
382 }, [canGenerate, canSend, onClickGenerate, onClickSend]);
380
383
381 useEffect(() => {
384 useEffect(() => {
382− const emails = inputs.map(i => i.data).filter(d => !!d);
383 if (uniq(emails).length !== emails.length) {
385 if (uniq(emails).length !== emails.length) {
384 setGeneralError(new Error(`Duplicate email.`));
386 setGeneralError(new Error(`Duplicate email.`));
385 } else {
387 } else {
386 setGeneralError(null);
388 setGeneralError(null);
387 }
389 }
388− }, [inputs]);
390+ }, [emails]);
389
391
390 return (
392 return (
391 <Grid gap={12}>
393 <Grid gap={12}>