@@ -1,13 +1,13 @@
1−import { Just, Nothing } from 'folktale/maybe';
2−import React from 'react';
1+import Maybe, { Just, Nothing } from 'folktale/maybe';
2+import React, { useState } from 'react';
33import * as azimuth from 'azimuth-js';
4−import * as need from 'lib/need';
4+import { Grid, Flex, Button, Input, IconButton } from 'indigo-react';
55
6+import * as need from 'lib/need';
67import { hasReceived, sendMail } from 'lib/inviteMail';
7−import { Button, Input, H3, Warning } from 'components/old/Base';
8+import * as wg from 'lib/walletgen';
89
9−// for wallet generation
10−import * as wg from '_walletgen/lib/lib';
10+import { H3, Warning } from 'components/old/Base';
1111
1212// for transaction generation and signing
1313import {
@@ -19,11 +19,17 @@ import {
1919 toWei,
2020} from 'lib/txn';
2121import * as tank from 'lib/tank';
22−import { withNetwork } from 'store/network';
23−import { compose } from 'lib/lib';
24−import { withWallet } from 'store/wallet';
25−import { withPointCursor } from 'store/pointCursor';
22+import { useLocalRouter } from 'lib/LocalRouter';
23+
2624import View from 'components/View';
25+import MiniBackButton from 'components/MiniBackButton';
26+import useRenderCount from 'lib/useRenderCount';
27+import useInvites from 'lib/useInvites';
28+import { usePointCursor } from 'store/pointCursor';
29+import LoadableButton from 'components/LoadableButton';
30+import useArray from 'lib/useArray';
31+import useForm from 'indigo-react/lib/useForm';
32+import { buildEmailInputConfig } from 'components/Inputs';
2733
2834const GAS_PRICE_GWEI = 20; // we pay the premium for faster ux
2935const GAS_LIMIT = 350000;
@@ -45,13 +51,90 @@ const EMAIL_STATUS = {
4551 FAIL: '×',
4652};
4753
54+// world's simplest uid
55+let id = 0;
56+
57+const buildInputConfig = (extra = {}) =>
58+ buildEmailInputConfig({ name: (id++).toString(), ...extra });
59+
60+export default function NewInviteEmail() {
61+ const { pop } = useLocalRouter();
62+ const { pointCursor } = usePointCursor();
63+ const point = need.pointCursor(pointCursor);
64+
65+ const { availableInvites } = useInvites(point);
66+ const maxInvitesToSend = availableInvites.matchWith({
67+ Nothing: () => 0,
68+ Just: p => p.value,
69+ });
70+
71+ const [
72+ inputConfigs,
73+ { append: appendInput, removeAt: removeInputAt },
74+ ] = useArray(
75+ [buildInputConfig({ placeholder: 'Email Addresss' })],
76+ buildInputConfig
77+ );
78+
79+ const [hovered, _setHovered] = useState({});
80+ const setHovered = (name, value) => () =>
81+ _setHovered((value = { ...value, [name]: value }));
82+
83+ console.log(inputConfigs.length, inputConfigs.map(c => c.name));
84+
85+ const { inputs, pass } = useForm(inputConfigs);
86+
87+ const canAddInvite = inputs.length < maxInvitesToSend;
88+
89+ useRenderCount('InviteEmail');
90+
91+ return (
92+ <Grid gap={12}>
93+ <Grid.Item as={Grid} className="mb4" full>
94+ <Grid.Item as={Flex} cols={[1, 11]} align="center">
95+ <MiniBackButton onClick={() => pop()} />
96+ </Grid.Item>
97+ <Grid.Item cols={[11, 13]} justifySelf="end">
98+ <IconButton onClick={appendInput} disabled={!canAddInvite} solid>
99+ +
100+ </IconButton>
101+ </Grid.Item>
102+ </Grid.Item>
103+
104+ {/* email inputs */}
105+ {inputs.map((input, i) => {
106+ const isFirst = i === 0;
107+ return (
108+ <Grid.Item
109+ key={input.name}
110+ as={Grid}
111+ gap={12}
112+ onMouseEnter={setHovered(input.name, true)}
113+ onMouseLeave={setHovered(input.name, false)}
114+ full>
115+ <Grid.Item as={Input} cols={[1, 11]} {...input} />
116+ {!isFirst && (input.focused || hovered[input.name]) && (
117+ <Grid.Item cols={[11, 13]} justifySelf="end" alignSelf="center">
118+ <IconButton onClick={() => removeInputAt(i)}>-</IconButton>
119+ </Grid.Item>
120+ )}
121+ </Grid.Item>
122+ );
123+ })}
124+
125+ <Grid.Item as={LoadableButton} full disabled={!pass} solid>
126+ Generate Invites
127+ </Grid.Item>
128+ </Grid>
129+ );
130+}
131+
48132class InviteEmail extends React.Component {
49133 constructor(props) {
50134 super(props);
51135
52136 this.state = {
53137 invitesAvailable: Nothing(),
54− invited: Nothing(),
55138 targets: [{ email: '', hasReceived: Nothing(), status: Nothing() }],
56139 status: STATUS.INPUT,
57140 errors: Nothing(),
@@ -60,7 +143,6 @@ class InviteEmail extends React.Component {
60143 wipInvites: [],
61144 };
62145
63− this.findInvited = this.findInvited.bind(this);
64146 this.generateInvites = this.generateInvites.bind(this);
65147 this.sendInvites = this.sendInvites.bind(this);
66148 this.canSend = this.canSend.bind(this);
@@ -78,33 +160,11 @@ class InviteEmail extends React.Component {
78160 this.contracts = need.contracts(this.props.contracts);
79161 this.web3 = need.web3(this.props.web3);
80162
81− console.log(azimuth.delegatedSending);
82163 azimuth.delegatedSending
83164 .getTotalUsableInvites(this.contracts, this.point)
84165 .then(count => {
85166 this.setState({ invitesAvailable: Just(count) });
86167 });
87− this.findInvited();
88− }
89−
90− componentDidUpdate(prevProps) {
91− //
92− }
93−
94− async findInvited() {
95− let invited = await azimuth.delegatedSending.getInvited(
96− this.contracts,
97− this.point
98− );
99− invited = invited.map(async point => {
100− const active = await azimuth.azimuth.isActive(this.contracts, point);
101− const res = { point: Number(point), active: active };
102− return res;
103− });
104− invited = await Promise.all(invited);
105− const total = invited.length;
106− const accepted = invited.filter(i => i.active).length;
107− this.setState({ invited: Just({ total, accepted }) });
108168 }
109169
110170 async getTemporaryWallet() {
@@ -347,30 +407,6 @@ class InviteEmail extends React.Component {
347407 const dif =
348408 this.state.status === STATUS.DONE ? this.state.targets.length : null;
349409
350− const invitesAvailable = this.state.invitesAvailable.matchWith({
351− Nothing: () => 'Loading...',
352− Just: invites => {
353− const dia = dif ? `(-${dif})` : '';
354− return `You currently have ${invites.value}${dia} invitations left.`;
355− },
356− });
357−
358− const invitesSent = this.state.invited.matchWith({
359− Nothing: () => <>{'Loading...'}</>,
360− Just: invited => {
361− const dit = dif ? `(+${dif})` : '';
362− return (
363− <>
364− {'Out of the '}
365− {invited.value.total + dit}
366− {' invites you sent, '}
367− {invited.value.accepted}
368− {' have been accepted.'}
369− </>
370− );
371− },
372− });
373−
374410 const error = this.state.errors.matchWith({
375411 Nothing: () => null,
376412 Just: errors => (
@@ -478,12 +514,6 @@ class InviteEmail extends React.Component {
478514
479515 return (
480516 <View>
481− <p>{'send invites here, for planets'}</p>
482−
483− <p>{invitesAvailable}</p>
484−
485− <ul>{invitesSent}</ul>
486−
487517 {error}
488518
489519 {fundingMessage}
@@ -494,8 +524,8 @@ class InviteEmail extends React.Component {
494524 }
495525}
496526
497−export default compose(
498− withNetwork,
499− withWallet,
500− withPointCursor
501−)(InviteEmail);
527+// export default compose(
528+// withNetwork,
529+// withWallet,
530+// withPointCursor
531+// )(InviteEmail);