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';
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: ';
37const InviteMail = ({ setSubmitting }) => {
46 const { sendMail, getHasReceived } = useMailer();
48 const [status, setStatus] = useState(STATUS.INPUT);
50 const [count, setCount] = useState(0);
51 const canInput = status === STATUS.INPUT;
52 const isSending = txStatus === STATUS.SENDING || status === STATUS.SENDING;
53 const isDone = status === STATUS.SUCCESS;
54 const isFailed = status === STATUS.FAILED;
56 const { pointCursor } = usePointCursor();
57 const point = need.point(pointCursor);
59 const sendInvites = useCallback(
60 async (emails, invites) => {
61 const mailingErrors = compact(
63 zip(emails, invites).map(([email, invite]) =>
64 sendMail(email, invite.ticket, ob.patp(point), '', invite.rawTx)
67 () => `Sending ticket ${invite.ticket} to ${email} failed.`
72 if (mailingErrors.length) {
73 return { [FORM_ERROR]: mailingErrors };
79 const onSubmit = useCallback(
80 async (values, form) => {
81 const emailCount = values.emails.length;
83 setStatus(STATUS.SENDING);
85 const { errors, invites } = await generateInvites(emailCount);
87 setStatus(STATUS.FAILED);
91 const mailErrors = await sendInvites(values.emails, invites);
93 console.log(mailErrors);
94 setStatus(STATUS.FAILED);
99 setStatus(STATUS.SUCCESS);
101 setStatus(STATUS.INPUT);
106 [setSubmitting, generateInvites, sendInvites, resetInvites]
109 const validateHasReceived = useCallback(
111 const hasReceived = await getHasReceived(email);
113 return HAS_RECEIVED_TEXT + email;
119 const buttonText = useCallback(() => {
122 return 'Add to Invite Group';
124 return `Sending ${pluralize(count, 'Invite')}...`;
126 return `✓ ${pluralize(count, 'Invite')} sent`;
133 const renderButton = useCallback(
137 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
144 const percentProgress = progress / count;
153 progress={percentProgress}>
160 <Grid.Item full className="mt4 p3 bg-green3 white f5">
167 <Grid.Item full className="mt4 p3 bg-red3 white f5">
175 [buttonText, isDone, isFailed, isSending, canInput, count, progress]
178 const validateForm = useCallback((values, errors) => {
179 if (hasErrors(errors)) {
183 // check for email uniqenesss
184 const emails = values.emails.filter(d => !!d);
185 if (uniq(emails).length !== emails.length) {
186 return { [FORM_ERROR]: 'Duplicate email.' };
190 const validate = useMemo(
194 emails: buildArrayValidator(buildEmailValidator(validateHasReceived)),
198 [validateHasReceived, validateForm]
201 const initialValues = { emails: [] };
203 <Grid.Item full as={Grid}>
206 initialValues={initialValues}
207 initialValuesEqual={isEqual}
209 {({ handleSubmit, values }) => (
211 <Grid.Item full as={EmailChipInput} name="emails" className="mt4" />
212 {renderButton(handleSubmit)}
214 <Grid.Item full as={FormError} />
216 <Grid.Item full as={NeedFundsNotice} {...needFunds} />
225export default InviteMail;
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list