on 6fbbd5e help sign in
214 lines 5.8 kB history raw
1import React, { useState, useCallback, useMemo } from 'react';
2import { Grid } from 'indigo-react';
3import { FORM_ERROR } from 'final-form';
4import * as ob from 'urbit-ob';
5import { uniq, zip, compact, isEqual } from 'lodash';
7import { usePointCursor } from 'store/pointCursor';
9import * as need from 'lib/need';
10import useMailer from 'lib/useMailer';
11import pluralize from 'lib/pluralize';
12import useInviter from 'lib/useInviter';
14import {
15 composeValidator,
16 hasErrors,
17 buildEmailValidator,
18 buildArrayValidator,
19} from 'form/validators';
20import EmailChipInput from 'form/EmailChipInput';
21import FormError from 'form/FormError';
22import SubmitButton from 'form/SubmitButton';
23import BridgeForm from 'form/BridgeForm';
25import NeedFundsNotice from 'components/NeedFundsNotice';
26import ProgressButton from 'components/ProgressButton';
28const HAS_RECEIVED_TEXT = 'This email has already received an invite: ';
30const STATUS = {
31 INPUT: 'INPUT',
32 SENDING: 'SENDING',
33 SUCCESS: 'SUCCESS',
34 FAILURE: 'FAILURE',
35};
37const InviteMail = ({ setSubmitting }) => {
38 const { progress, txStatus, needFunds, generateInvites } = useInviter();
40 const { sendMail, getHasReceived } = useMailer();
42 const [status, setStatus] = useState(STATUS.INPUT);
44 const [count, setCount] = useState(0);
45 const canInput = status === STATUS.INPUT;
46 const isSending = txStatus === STATUS.SENDING || status === STATUS.SENDING;
47 const isDone = status === STATUS.SUCCESS;
48 const isFailed = status === STATUS.FAILED;
50 const { pointCursor } = usePointCursor();
51 const point = need.point(pointCursor);
53 const sendInvites = useCallback(
54 async (emails, invites) => {
55 const mailingErrors = compact(
56 await Promise.all(
57 zip(emails, invites).map(([email, invite]) =>
58 sendMail(email, invite.ticket, ob.patp(point), '', invite.rawTx)
59 .then(() => null)
60 .catch(
61 () => `Sending ticket ${invite.ticket} to ${email} failed.`
62 )
63 )
64 )
65 ).join(', ');
66 if (mailingErrors.length) {
67 return { [FORM_ERROR]: mailingErrors };
68 }
69 },
70 [point, sendMail]
71 );
73 const onSubmit = useCallback(
74 async values => {
75 const emailCount = values.emails.length;
76 setCount(emailCount);
77 setStatus(STATUS.SENDING);
78 setSubmitting(true);
79 const { errors, invites } = await generateInvites(emailCount);
80 if (errors) {
81 setStatus(STATUS.FAILED);
82 setSubmitting(false);
83 return errors;
84 }
85 const mailErrors = await sendInvites(values.emails, invites);
86 if (mailErrors) {
87 console.log(mailErrors);
88 setStatus(STATUS.FAILED);
89 setSubmitting(false);
90 return mailErrors;
91 }
92 setSubmitting(false);
93 setStatus(STATUS.SUCCESS);
94 },
95 [generateInvites, setCount, setStatus, sendInvites, setSubmitting]
96 );
98 const validateHasReceived = useCallback(
99 async email => {
100 const hasReceived = await getHasReceived(email);
101 if (hasReceived) {
102 return HAS_RECEIVED_TEXT + email;
103 }
104 },
105 [getHasReceived]
106 );
108 const buttonText = useCallback(() => {
109 switch (status) {
110 case STATUS.INPUT:
111 return 'Add to Invite Group';
112 case STATUS.SENDING:
113 return `Sending ${pluralize(count, 'Invite')}...`;
114 case STATUS.SUCCESS:
115 return `✓ ${pluralize(count, 'Invite')} sent`;
116 case STATUS.FAILURE:
117 default:
118 return 'Error';
119 }
120 }, [status, count]);
122 const renderButton = useCallback(
123 handleSubmit => {
124 if (canInput) {
125 return (
126 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
127 {buttonText()}
128 </Grid.Item>
129 );
130 }
132 if (isSending) {
133 const percentProgress = progress / count;
134 return (
135 <Grid.Item
136 full
137 as={ProgressButton}
138 className="mt4"
139 solid
140 disabled
141 success
142 progress={percentProgress}>
143 {buttonText(count)}
144 </Grid.Item>
145 );
146 }
147 if (isDone) {
148 return (
149 <Grid.Item full className="mt4 p3 bg-green3 white f5">
150 {buttonText(count)}
151 </Grid.Item>
152 );
153 }
154 if (isFailed) {
155 return (
156 <Grid.Item full className="mt4 p3 bg-red3 white f5">
157 {buttonText(count)}
158 </Grid.Item>
159 );
160 }
162 return null;
163 },
164 [buttonText, isDone, isFailed, isSending, canInput, count, progress]
165 );
167 const validateForm = useCallback((values, errors) => {
168 if (hasErrors(errors)) {
169 return errors;
170 }
172 // check for email uniqenesss
173 const emails = values.emails.filter(d => !!d);
174 if (uniq(emails).length !== emails.length) {
175 return { [FORM_ERROR]: 'Duplicate email.' };
176 }
177 }, []);
179 const validate = useMemo(
180 () =>
181 composeValidator(
182 {
183 emails: buildArrayValidator(buildEmailValidator(validateHasReceived)),
184 },
185 validateForm
186 ),
187 [validateHasReceived, validateForm]
188 );
190 const initialValues = { emails: [] };
191 return (
192 <Grid.Item full as={Grid}>
193 <BridgeForm
194 validate={validate}
195 initialValues={initialValues}
196 initialValuesEqual={isEqual}
197 onSubmit={onSubmit}>
198 {({ handleSubmit, values }) => (
199 <>
200 <Grid.Item full as={EmailChipInput} name="emails" className="mt4" />
201 {renderButton(handleSubmit)}
203 <Grid.Item full as={FormError} />
204 {needFunds && (
205 <Grid.Item full as={NeedFundsNotice} {...needFunds} />
206 )}
207 </>
208 )}
209 </BridgeForm>
210 </Grid.Item>
211 );
212};
214export default InviteMail;