on master help sign in
Merge branch 'master' into sigil-generator
97280b6Liam Fitzgerald
package-lock.json+91−29
@@ -1448,54 +1448,116 @@
1448 }
1448 }
1449 },
1449 },
1450 "@ledgerhq/devices": {
1450 "@ledgerhq/devices": {
1451− "version": "5.0.0",
1451+ "version": "5.5.0",
1452− "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-5.0.0.tgz",
1452+ "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-5.5.0.tgz",
1453− "integrity": "sha512-wEnf2RMFELgj4q1ApG2bNzyobYgeX3Jl92FA8af/1VfcBB9esApFAO8cuE7aQruv2S6k5sTR5RMMnomrS9myiw==",
1453+ "integrity": "sha512-t3Xl2ObF4yTVz3rEdtETv1Vjy1FYQVcxAoTV52S2I/mT3Ac/z7UWBjKG/w4YRc/Itn371PkTguMoSKDL2GXq3g==",
1454 "requires": {
1454 "requires": {
1455− "@ledgerhq/errors": "^5.0.0",
1455+ "@ledgerhq/errors": "^5.5.0",
1456− "@ledgerhq/logs": "^5.0.0",
1456+ "@ledgerhq/logs": "^5.3.0",
1457− "rxjs": "^6.5.3"
1457+ "rxjs": "^6.5.4"
1458+ },
1459+ "dependencies": {
1460+ "rxjs": {
1461+ "version": "6.5.4",
1462+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
1463+ "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
1464+ "requires": {
1465+ "tslib": "^1.9.0"
1466+ }
1467+ }
1458 }
1468 }
1459 },
1469 },
1460 "@ledgerhq/errors": {
1470 "@ledgerhq/errors": {
1461− "version": "5.0.0",
1471+ "version": "5.5.0",
1462− "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-5.0.0.tgz",
1472+ "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-5.5.0.tgz",
1463− "integrity": "sha512-sguPj6FeYOdkjuQEBfZSM6j5xYBk9RK1L9AfkLlO0zXSYHFZ+9OAlTA6Q0d5W7xmktx+32tUYxiT+JQ9WaeGmw=="
1473+ "integrity": "sha512-e6VlIVC3QR8EHXm/BtBcOeo+KqIKmeufr9cg38CK2KOD6CAEGJseipkyxVWWS0AR+kJIvWHl7aFqn2OdXe9EDQ=="
1464 },
1474 },
1465 "@ledgerhq/hw-app-eth": {
1475 "@ledgerhq/hw-app-eth": {
1466− "version": "5.1.0",
1476+ "version": "5.5.0",
1467− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-5.1.0.tgz",
1477+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-app-eth/-/hw-app-eth-5.5.0.tgz",
1468− "integrity": "sha512-TjOVdDV4J6+yfEnF8TI5e5RjbP9tI16MUsHHGWNVH8NFK34KJjDJurcjVECktDXsMxYXldlPS50otCSwJqn8JQ==",
1478+ "integrity": "sha512-hP3ouvX/tukriLGS6i7GJ89VmeXx3siWbbyR9kR9ee7v9qPc2k3Y1X9R+CosvN/i//Dx+5VSr1KbyzyMGEhJgA==",
1469 "requires": {
1479 "requires": {
1470− "@ledgerhq/errors": "^5.0.0",
1480+ "@ledgerhq/errors": "^5.5.0",
1481+ "@ledgerhq/hw-transport": "^5.5.0"
1482+ },
1483+ "dependencies": {
1484+ "@ledgerhq/devices": {
1485+ "version": "5.5.0",
1486+ "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-5.5.0.tgz",
1487+ "integrity": "sha512-t3Xl2ObF4yTVz3rEdtETv1Vjy1FYQVcxAoTV52S2I/mT3Ac/z7UWBjKG/w4YRc/Itn371PkTguMoSKDL2GXq3g==",
1488+ "requires": {
1489+ "@ledgerhq/errors": "^5.5.0",
1490+ "@ledgerhq/logs": "^5.3.0",
1491+ "rxjs": "^6.5.4"
1492+ }
1493+ },
1494+ "@ledgerhq/errors": {
1495+ "version": "5.5.0",
1496+ "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-5.5.0.tgz",
1497+ "integrity": "sha512-e6VlIVC3QR8EHXm/BtBcOeo+KqIKmeufr9cg38CK2KOD6CAEGJseipkyxVWWS0AR+kJIvWHl7aFqn2OdXe9EDQ=="
1498+ },
1471− "@ledgerhq/hw-transport": "^5.0.0"
1499+ "@ledgerhq/hw-transport": {
1500+ "version": "5.5.0",
1501+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-5.5.0.tgz",
1502+ "integrity": "sha512-VetoCESuyjYhkKAx61e+8B9c279c7BiWjp0uoZctsWp+M1PrzPobirKO2Yr1mFnoJSY87uuZhCEAOW3mfac8QQ==",
1503+ "requires": {
1504+ "@ledgerhq/devices": "^5.5.0",
1505+ "@ledgerhq/errors": "^5.5.0",
1506+ "events": "^3.1.0"
1507+ }
1508+ },
1509+ "@ledgerhq/logs": {
1510+ "version": "5.3.0",
1511+ "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-5.3.0.tgz",
1512+ "integrity": "sha512-OeCSsXM6WuFLpNI4s9Lr69xwb/blmE4K67AlFTPRW6Xc4miXXqRVn0YmEkEi/4h7iS/3Aq24cytZawBzr+evdw=="
1513+ },
1514+ "events": {
1515+ "version": "3.1.0",
1516+ "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
1517+ "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg=="
1518+ },
1519+ "rxjs": {
1520+ "version": "6.5.4",
1521+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
1522+ "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
1523+ "requires": {
1524+ "tslib": "^1.9.0"
1525+ }
1526+ }
1472 }
1527 }
1473 },
1528 },
1474 "@ledgerhq/hw-transport": {
1529 "@ledgerhq/hw-transport": {
1475− "version": "5.0.0",
1530+ "version": "5.5.0",
1476− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-5.0.0.tgz",
1531+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-5.5.0.tgz",
1477− "integrity": "sha512-TS2h3RmFQj6igKGfE8ThN1Mk8+w4lK+xHvrDUymkJHzbQhboRTGcgp6psg5F3L2ZYKIq8Q3NFDSqhfygI2ZFqg==",
1532+ "integrity": "sha512-VetoCESuyjYhkKAx61e+8B9c279c7BiWjp0uoZctsWp+M1PrzPobirKO2Yr1mFnoJSY87uuZhCEAOW3mfac8QQ==",
1478 "requires": {
1533 "requires": {
1479− "@ledgerhq/devices": "^5.0.0",
1534+ "@ledgerhq/devices": "^5.5.0",
1480− "@ledgerhq/errors": "^5.0.0",
1535+ "@ledgerhq/errors": "^5.5.0",
1536+ "events": "^3.1.0"
1537+ },
1538+ "dependencies": {
1481− "events": "^3.0.0"
1539+ "events": {
1540+ "version": "3.1.0",
1541+ "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
1542+ "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg=="
1543+ }
1482 }
1544 }
1483 },
1545 },
1484 "@ledgerhq/hw-transport-u2f": {
1546 "@ledgerhq/hw-transport-u2f": {
1485− "version": "5.0.0",
1547+ "version": "5.5.0",
1486− "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-5.0.0.tgz",
1548+ "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-5.5.0.tgz",
1487− "integrity": "sha512-8936tpH5Ey8tQaycL2iTRWpb/dc5XgFnrCtzRPXiF/UbtIZ6Cy3l9G6Wd/OyzwipT1AZbJNf4l07WUOJFUcL7w==",
1549+ "integrity": "sha512-+bvFwW+adP9Ak4lCovtI0j1KTWgeUlMjnneuzuu7P9N7REAMGX68+yAslwVl4cs+8u7HbT/XN8A3O3kuR8yyXA==",
1488 "requires": {
1550 "requires": {
1489− "@ledgerhq/errors": "^5.0.0",
1551+ "@ledgerhq/errors": "^5.5.0",
1490− "@ledgerhq/hw-transport": "^5.0.0",
1552+ "@ledgerhq/hw-transport": "^5.5.0",
1491− "@ledgerhq/logs": "^5.0.0",
1553+ "@ledgerhq/logs": "^5.3.0",
1492 "u2f-api": "0.2.7"
1554 "u2f-api": "0.2.7"
1493 }
1555 }
1494 },
1556 },
1495 "@ledgerhq/logs": {
1557 "@ledgerhq/logs": {
1496− "version": "5.0.0",
1558+ "version": "5.3.0",
1497− "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-5.0.0.tgz",
1559+ "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-5.3.0.tgz",
1498− "integrity": "sha512-+Rgnp0T5ximUUwEUCSRbJNTdfVFrchO7sqY4MtVChXSMqu8vvzeg9KKakAE6t9IKEnoQ7aaG/BIEDtLXvrgRBQ=="
1560+ "integrity": "sha512-OeCSsXM6WuFLpNI4s9Lr69xwb/blmE4K67AlFTPRW6Xc4miXXqRVn0YmEkEi/4h7iS/3Aq24cytZawBzr+evdw=="
1499 },
1561 },
1500 "@mrmlnc/readdir-enhanced": {
1562 "@mrmlnc/readdir-enhanced": {
1501 "version": "2.2.1",
1563 "version": "2.2.1",
package.json+2−2
@@ -15,8 +15,8 @@
15 },
15 },
16 "dependencies": {
16 "dependencies": {
17 "@hot-loader/react-dom": "^16.8.6",
17 "@hot-loader/react-dom": "^16.8.6",
18− "@ledgerhq/hw-app-eth": "^5.1.0",
18+ "@ledgerhq/hw-app-eth": "^5.5.0",
19− "@ledgerhq/hw-transport-u2f": "^5.0.0",
19+ "@ledgerhq/hw-transport-u2f": "^5.5.0",
20 "@welldone-software/why-did-you-render": "^3.2.3",
20 "@welldone-software/why-did-you-render": "^3.2.3",
21 "async-retry": "^1.2.3",
21 "async-retry": "^1.2.3",
22 "azimuth-js": "^0.17.0",
22 "azimuth-js": "^0.17.0",
src/components/Chip.js+2−4
@@ -1,14 +1,12 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3
3
4−export default function Chip({ children, color, className }) {
4+export default function Chip({ children, bgColor, fgColor, className }) {
5− const bgColor = `bg-${color}1`;
6− const fgColor = `${color}4`;
7 return (
5 return (
8 <div
6 <div
9 className={cn(
7 className={cn(
10 className,
8 className,
11− 'h6 r-full flex-center ph2 mh2',
9+ 'h6 r-full flex-center ph2 mh2 f6',
12 bgColor,
10 bgColor,
13 fgColor
11 fgColor
14 )}>
12 )}>
src/components/InviteSigilList.js+6−8
@@ -5,7 +5,6 @@ import * as ob from 'urbit-ob';
5import { take } from 'lodash';
5import { take } from 'lodash';
6
6
7import MaybeSigil from 'components/MaybeSigil';
7import MaybeSigil from 'components/MaybeSigil';
8−import WithTooltip from 'components/WithTooltip';
9
8
10const InviteSigilList = ({ className, pendingPoints, acceptedPoints }) => {
9const InviteSigilList = ({ className, pendingPoints, acceptedPoints }) => {
11 const _acceptedPoints = take(
10 const _acceptedPoints = take(
@@ -24,16 +23,15 @@ const InviteSigilList = ({ className, pendingPoints, acceptedPoints }) => {
24 ).keys(),
23 ).keys(),
25 ].map(() => Nothing());
24 ].map(() => Nothing());
26
25
27− const tooltip = point => point.getOrElse(null);
28−
29 const renderSigil = (points, colors, klassName) => {
26 const renderSigil = (points, colors, klassName) => {
30 return (
27 return (
31 <>
28 <>
32 {points.map((point, idx) => (
29 {points.map((point, idx) => (
30+ <div
31+ key={idx}
32+ title={point.getOrElse(null)}
33− <div key={idx} className={cn(klassName, 'h9 w9')}>
33+ className={cn(klassName, 'h9 w9')}>
34− <WithTooltip content={tooltip(point)}>
35− <MaybeSigil patp={point} size={50} colors={colors} />
34+ <MaybeSigil patp={point} size={50} colors={colors} />
36− </WithTooltip>
37 </div>
35 </div>
38 ))}
36 ))}
39 </>
37 </>
@@ -44,7 +42,7 @@ const InviteSigilList = ({ className, pendingPoints, acceptedPoints }) => {
44 <div className={cn('flex justify-between', className)}>
42 <div className={cn('flex justify-between', className)}>
45 {renderSigil(_acceptedPoints, ['#000000', '#FFFFFF'])}
43 {renderSigil(_acceptedPoints, ['#000000', '#FFFFFF'])}
46 {renderSigil(_pendingPoints, ['#ee892b', '#FFFFFF'])}
44 {renderSigil(_pendingPoints, ['#ee892b', '#FFFFFF'])}
47− {renderSigil(empty, [], 'b1 b-black b-dashed')}
45+ {renderSigil(empty, [], 'b1 b-gray3 b-dashed')}
48 </div>
46 </div>
49 );
47 );
50};
48};
src/components/Tabs.js+6−4
@@ -8,7 +8,7 @@ export default function Tabs({
8 currentTab,
8 currentTab,
9 onTabChange,
9 onTabChange,
10 className,
10 className,
11− tabClassName,
11+ center = false,
12 ...rest
12 ...rest
13}) {
13}) {
14 const Tab = views[currentTab];
14 const Tab = views[currentTab];
@@ -26,17 +26,19 @@ export default function Tabs({
26 key={option.value}
26 key={option.value}
27 onClick={() => onTabChange(option.value)}
27 onClick={() => onTabChange(option.value)}
28 className={cn(
28 className={cn(
29− tabClassName,
30 'f5 pv3 pointer nowrap',
29 'f5 pv3 pointer nowrap',
31 {
30 {
32 'black b-black bb1': isActive,
31 'black b-black bb1': isActive,
33 gray3: !isActive,
32 gray3: !isActive,
34 },
33 },
34+ {
35+ 't-center flex1': center,
36+ },
35 {
37 {
36 // all items have right margin/padding
38 // all items have right margin/padding
37− 'mr2 pr2': true,
39+ 'mr2 pr2': !center,
38 // the first one is flush to the left
40 // the first one is flush to the left
39− 'ml2 pl2': !isFirst,
41+ 'ml2 pl2': !isFirst && !center,
40 }
42 }
41 )}>
43 )}>
42 {option.text}
44 {option.text}
src/form/EmailChipInput.js+1−1
@@ -102,7 +102,7 @@ const EmailChipInput = ({ className, name, label, disabled }) => {
102
102
103 <input
103 <input
104 type="textarea"
104 type="textarea"
105− className="mv1 mh2 h7 b-none flex-grow"
105+ className="mv1 mh2 h7 b-none flex1"
106 placeholder="Enter an email address"
106 placeholder="Enter an email address"
107 value={value}
107 value={value}
108 onChange={handleChange}
108 onChange={handleChange}
src/indigo-react/components/Button.js+7−2
@@ -19,6 +19,7 @@ export default function Button({
19 background,
19 background,
20 type,
20 type,
21 children,
21 children,
22+ center = false,
22 ...rest
23 ...rest
23}) {
24}) {
24 const handleKeyPress = e => {
25 const handleKeyPress = e => {
@@ -60,10 +61,14 @@ export default function Button({
60 onClick={!disabled && onClick ? onClick : undefined}
61 onClick={!disabled && onClick ? onClick : undefined}
61 {...rest}>
62 {...rest}>
62 {background}
63 {background}
64+ <Grid.Item
65+ full
66+ as={Flex}
63− <Grid.Item full as={Flex} justify="between" className="z2">
67+ justify="between"
68+ className={cn('z2', { 'flex-center': center })}>
64 <span>{children}</span>
69 <span>{children}</span>
65 {type && <button type={type} style={{ display: 'none' }}></button>}
70 {type && <button type={type} style={{ display: 'none' }}></button>}
66− <div className={cn('pl4')}>{accessory}</div>
71+ {!center && <div className={cn('pl4')}>{accessory}</div>}
67 </Grid.Item>
72 </Grid.Item>
68 {detail && (
73 {detail && (
69 <Grid.Item full as={HelpText} className={cn('z2', detailClassName)}>
74 <Grid.Item full as={HelpText} className={cn('z2', detailClassName)}>
src/views/Invite/Cohort.js+113−39
@@ -1,5 +1,5 @@
1import React, { useMemo, useEffect, useCallback, useState } from 'react';
1import React, { useMemo, useEffect, useCallback, useState } from 'react';
2−import { Grid, Flex } from 'indigo-react';
2+import { Grid, Flex, Button } from 'indigo-react';
3import * as ob from 'urbit-ob';
3import * as ob from 'urbit-ob';
4import { Just } from 'folktale/maybe';
4import { Just } from 'folktale/maybe';
5import cn from 'classnames';
5import cn from 'classnames';
@@ -8,10 +8,11 @@ import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
8import { usePointCache } from 'store/pointCache';
9
9
10import View from 'components/View';
10import View from 'components/View';
11+import Tabs from 'components/Tabs';
11import BarGraph from 'components/BarGraph';
12import BarGraph from 'components/BarGraph';
12−import InviteSigilList from 'components/InviteSigilList';
13import MaybeSigil from 'components/MaybeSigil';
13import MaybeSigil from 'components/MaybeSigil';
14import { matchBlinky } from 'components/Blinky';
14import { matchBlinky } from 'components/Blinky';
15+import { ForwardButton } from 'components/Buttons';
15import Chip from 'components/Chip';
16import Chip from 'components/Chip';
16import Crumbs from 'components/Crumbs';
17import Crumbs from 'components/Crumbs';
17
18
@@ -19,12 +20,38 @@ import { useLocalRouter } from 'lib/LocalRouter';
19import useInvites from 'lib/useInvites';
20import useInvites from 'lib/useInvites';
20import * as need from 'lib/need';
21import * as need from 'lib/need';
21
22
23+import Inviter from 'views/Invite/Inviter';
24+
22import { ReactComponent as SearchIcon } from 'assets/search.svg';
25import { ReactComponent as SearchIcon } from 'assets/search.svg';
23
26
27+function SearchInput({ className, value, onChange }) {
28+ return (
29+ <Flex align="center" full className={cn('b b-gray2 b1', className)}>
30+ <Flex.Item className="p2">
31+ <SearchIcon />
32+ </Flex.Item>
33+
34+ <Flex.Item
35+ flex={1}
36+ as={'input'}
37+ value={value}
38+ onChange={onChange}
39+ className="b-none h7"
40+ />
41+ </Flex>
42+ );
43+}
44+
24−function CohortMember({ point, className }) {
45+function CohortMember({ point, pending = false, className }) {
25 const { sentInvites } = useInvites(point);
46 const { sentInvites } = useInvites(point);
26 const patp = useMemo(() => ob.patp(point), [point]);
47 const patp = useMemo(() => ob.patp(point), [point]);
27− const colors = ['#000000', '#FFFFFF'];
48+ const colors = pending ? ['#ee892b', '#FFFFFF'] : ['#000000', '#FFFFFF'];
49+
50+ const DetailText = useCallback(
51+ () =>
52+ pending ? 'Pending' : <> {matchBlinky(sentInvites)} points invited </>,
53+ [sentInvites]
54+ );
28
55
29 return (
56 return (
30 <Flex justify="between" className={cn('b1 b-gray2', className)}>
57 <Flex justify="between" className={cn('b1 b-gray2', className)}>
@@ -39,19 +66,21 @@ function CohortMember({ point, className }) {
39 col
66 col
40 className="mono f6 ph4">
67 className="mono f6 ph4">
41 <Flex.Item>{patp}</Flex.Item>
68 <Flex.Item>{patp}</Flex.Item>
42− <Flex.Item>{matchBlinky(sentInvites)} points invited</Flex.Item>
69+ <Flex.Item className="gray4">
70+ <DetailText />
71+ </Flex.Item>
43 </Flex.Item>
72 </Flex.Item>
44 </Flex>
73 </Flex>
45 );
74 );
46}
75}
47−function CohortList({ points, className }) {
76+function CohortList({ acceptedPoints, pendingPoints, className, onlyPending }) {
48 const { syncInvites } = usePointCache();
77 const { syncInvites } = usePointCache();
49
78
50 const [query, setQuery] = useState('');
79 const [query, setQuery] = useState('');
51
80
52 useEffect(() => {
81 useEffect(() => {
53− points.map(syncInvites);
82+ acceptedPoints.map(syncInvites);
54− }, [points, syncInvites]);
83+ }, [acceptedPoints, syncInvites]);
55
84
56 const handleChange = useCallback(
85 const handleChange = useCallback(
57 e => {
86 e => {
@@ -61,40 +90,51 @@ function CohortList({ points, className }) {
61 [setQuery]
90 [setQuery]
62 );
91 );
63
92
64− const visiblePoints = useMemo(
93+ const filterPoints = useCallback(
65− () =>
94+ points =>
66 points.filter(p => {
95 points.filter(p => {
67 const patp = ob.patp(p).slice(1);
96 const patp = ob.patp(p).slice(1);
68 return patp.startsWith(query);
97 return patp.startsWith(query);
69 }),
98 }),
70− [query, points]
99+ [query]
71 );
100 );
72
101
73− return (
102+ const _pendingPoints = filterPoints(pendingPoints);
74− <Grid gap={3} className={cn('mt1', className)}>
103+ const _acceptedPoints = filterPoints(acceptedPoints);
75− <Grid.Item align="center" full as={Flex} className="b b-gray2 b1">
76− <Flex.Item>
77− <SearchIcon className="p2" />
78− </Flex.Item>
79
104
80− <Flex.Item
105+ const offset = onlyPending ? 0 : _acceptedPoints.length;
81− flex={1}
106+
82− as={'input'}
107+ return (
83− value={query}
108+ <Grid gap={3} className={cn('mt4', className)}>
84− onChange={handleChange}
109+ <Grid.Item full as={SearchInput} value={query} onChange={handleChange} />
85− className="b-none"
86− />
87− </Grid.Item>
88 <>
110 <>
89− {visiblePoints.map((p, idx) => (
111+ {!onlyPending &&
112+ _acceptedPoints.map((p, idx) => (
113+ <Grid.Item
114+ key={p}
115+ half={(idx % 2) + 1}
90− <Grid.Item key={p} half={(idx % 2) + 1} as={CohortMember} point={p} />
116+ as={CohortMember}
117+ point={p}
118+ />
119+ ))}
120+ {_pendingPoints.map((p, idx) => (
121+ <Grid.Item
122+ key={p}
123+ half={((offset + idx) % 2) + 1}
124+ as={CohortMember}
125+ point={p}
126+ pending
127+ />
91 ))}
128 ))}
92 </>
129 </>
93− {visiblePoints.length === 0 && (
130+
131+ {_pendingPoints.length === 0 &&
132+ (_acceptedPoints.length === 0 || onlyPending) && (
94− <Grid.Item full className="p4 t-center">
133+ <Grid.Item full className="p4 t-center">
134+ {' '}
95− No invites accepted yet.
135+ No invites accepted yet.
96− </Grid.Item>
136+ </Grid.Item>
97− )}
137+ )}
98 </Grid>
138 </Grid>
99 );
139 );
100}
140}
@@ -113,6 +153,9 @@ export default function InviteCohort() {
113 sentInvites,
153 sentInvites,
114 } = useInvites(point);
154 } = useInvites(point);
115
155
156+ const [showInviteForm, setShowInviteForm] = useState(false);
157+ const [tab, setTab] = useState(NAMES.ALL);
158+
116 const _acceptedPoints = acceptedPoints.getOrElse([]);
159 const _acceptedPoints = acceptedPoints.getOrElse([]);
117 const _pendingPoints = pendingPoints.getOrElse([]);
160 const _pendingPoints = pendingPoints.getOrElse([]);
118 const _pendingInvites = _pendingPoints.length;
161 const _pendingInvites = _pendingPoints.length;
@@ -138,10 +181,10 @@ export default function InviteCohort() {
138 <Grid.Item full>
181 <Grid.Item full>
139 <Flex align="center">
182 <Flex align="center">
140 <Flex.Item>
183 <Flex.Item>
141− {_acceptedInvites + _pendingInvites} / {_totalInvites}
184+ {_acceptedInvites} / {_totalInvites}
142 </Flex.Item>
185 </Flex.Item>
143− {_pendingInvites && (
186+ {_pendingInvites !== 0 && (
144− <Flex.Item as={Chip} color="yellow">
187+ <Flex.Item as={Chip} className="bg-yellow4 white">
145 {_pendingInvites} pending
188 {_pendingInvites} pending
146 </Flex.Item>
189 </Flex.Item>
147 )}
190 )}
@@ -154,14 +197,45 @@ export default function InviteCohort() {
154 sent={sentInvites}
197 sent={sentInvites}
155 accepted={acceptedInvites}
198 accepted={acceptedInvites}
156 />
199 />
200+ {availableInvites.getOrElse(0) !== 0 && !showInviteForm && (
201+ <Grid.Item
202+ full
203+ solid
204+ center
205+ as={Button}
206+ onClick={() => setShowInviteForm(true)}>
207+ Add Members
208+ </Grid.Item>
209+ )}
210+ {showInviteForm && <Inviter />}
211+
157 <Grid.Item
212 <Grid.Item
158 full
213 full
159− as={InviteSigilList}
214+ center
160− pendingPoints={pendingPoints}
215+ as={Tabs}
216+ views={VIEWS}
217+ options={OPTIONS}
218+ currentTab={tab}
219+ onTabChange={setTab}
161− acceptedPoints={acceptedPoints}
220+ acceptedPoints={_acceptedPoints}
221+ pendingPoints={_pendingPoints}
222+ onlyPending={tab === NAMES.PENDING}
162 />
223 />
163− <Grid.Item full as={CohortList} points={_acceptedPoints} />
164 </Grid>
224 </Grid>
165 </View>
225 </View>
166 );
226 );
167}
227}
228+const NAMES = {
229+ ALL: 'ALL',
230+ PENDING: 'PENDING',
231+};
232+
233+const VIEWS = {
234+ [NAMES.PENDING]: CohortList,
235+ [NAMES.ALL]: CohortList,
236+};
237+
238+const OPTIONS = [
239+ { text: 'All', value: NAMES.ALL },
240+ { text: 'Pending', value: NAMES.PENDING },
241+];
src/views/Invite/Email.js+1−1
@@ -92,7 +92,7 @@ const InviteMail = ({ setSubmitting }) => {
92 setSubmitting(false);
92 setSubmitting(false);
93 setStatus(STATUS.SUCCESS);
93 setStatus(STATUS.SUCCESS);
94 },
94 },
95− [generateInvites, setCount, setStatus, sendInvites]
95+ [generateInvites, setCount, setStatus, sendInvites, setSubmitting]
96 );
96 );
97
97
98 const validateHasReceived = useCallback(
98 const validateHasReceived = useCallback(
src/views/Invite/Inviter.js+1−1
@@ -25,7 +25,7 @@ const Inviter = () => {
25 <Grid.Item
25 <Grid.Item
26 full
26 full
27 as={Tabs}
27 as={Tabs}
28− tabClassName="t-center flex1"
28+ center
29 className="flex1"
29 className="flex1"
30 views={VIEWS}
30 views={VIEWS}
31 options={OPTIONS}
31 options={OPTIONS}
src/views/Invite/Url.js+17−14
@@ -1,5 +1,5 @@
1import React, { useCallback, useState } from 'react';
1import React, { useCallback, useState } from 'react';
2−import { Grid, ErrorText } from 'indigo-react';
2+import { Grid, ErrorText, Flex } from 'indigo-react';
3import { get } from 'lodash';
3import { get } from 'lodash';
4import { FORM_ERROR } from 'final-form';
4import { FORM_ERROR } from 'final-form';
5
5
@@ -30,7 +30,8 @@ const InviteUrl = ({ setSubmitting }) => {
30 if (hasErrors(errors)) {
30 if (hasErrors(errors)) {
31 setError(errors);
31 setError(errors);
32 }
32 }
33+ setSubmitting(false);
33− }, [generateInvites]);
34+ }, [generateInvites, setSubmitting]);
34
35
35 const renderGenerateButton = useCallback(() => {
36 const renderGenerateButton = useCallback(() => {
36 if (error) {
37 if (error) {
@@ -70,26 +71,28 @@ const InviteUrl = ({ setSubmitting }) => {
70 }
71 }
71 }, [txStatus, error, generateInvite]);
72 }, [txStatus, error, generateInvite]);
72
73
73− const url = `https://bridge.urbit.org/#${get(invites, '[0].ticket', '')}`;
74+ const url = `https://bridge.urbit.org/#${get(invites, '[0].ticket', '').slice(
75+ 1
76+ )}`;
74
77
75 const [doCopy, didCopy] = useCopiable(url);
78 const [doCopy, didCopy] = useCopiable(url);
76
79
77 return (
80 return (
78− <Grid.Item full as={Grid}>
81+ <Grid.Item full as={Grid} gap={3}>
79 {invites.length > 0 && (
82 {invites.length > 0 && (
80− <>
83+ <Grid.Item as={Flex} full col>
81− <Grid.Item full className="b-gray3 b1 mv4 p1 flex flex-center">
84+ <Flex.Item full className="b-gray3 b1 mt4 mb1 p1 flex flex-center">
82− <div className="flex1 ml1">{url}</div>
85+ <div className="flex1 ml1 f6">{url}</div>
83− <Grid.Item
86+ <Flex.Item
84− className="pv3 ph4 bg-black white"
87+ className="p2 bg-black white pointer-hover f6"
85 onClick={() => doCopy()}>
88 onClick={() => doCopy()}>
86 {didCopy ? 'Copied' : 'Copy'}
89 {didCopy ? 'Copied' : 'Copy'}
87− </Grid.Item>
90+ </Flex.Item>
88− </Grid.Item>
91+ </Flex.Item>
89− <Grid.Item full>
92+ <Flex.Item full className="f6 mb4 gray5">
90 Your invite link expires after consumption.
93 Your invite link expires after consumption.
91− </Grid.Item>
94+ </Flex.Item>
92− </>
95+ </Grid.Item>
93 )}
96 )}
94 {invites.length === 0 && renderGenerateButton()}
97 {invites.length === 0 && renderGenerateButton()}
95 {needFunds && <Grid.Item full as={NeedFundsNotice} {...needFunds} />}
98 {needFunds && <Grid.Item full as={NeedFundsNotice} {...needFunds} />}
src/views/Point.js+7−6
@@ -1,7 +1,7 @@
1import React, { useCallback, useState } from 'react';
1import React, { useCallback, useState } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Just } from 'folktale/maybe';
3import { Just } from 'folktale/maybe';
4−import { Grid, Flex } from 'indigo-react';
4+import { Grid, Flex, Button } from 'indigo-react';
5import { azimuth } from 'azimuth-js';
5import { azimuth } from 'azimuth-js';
6
6
7import { usePointCursor } from 'store/pointCursor';
7import { usePointCursor } from 'store/pointCursor';
@@ -148,7 +148,7 @@ export default function Point() {
148 </Grid.Item>
148 </Grid.Item>
149
149
150 <Grid.Item
150 <Grid.Item
151− className={cn('t-right underline', {
151+ className={cn('t-right underline pointer-hover', {
152 gray4: sentInvites.getOrElse(0) === 0,
152 gray4: sentInvites.getOrElse(0) === 0,
153 })}
153 })}
154 onClick={goCohort}
154 onClick={goCohort}
@@ -158,11 +158,10 @@ export default function Point() {
158 <Grid.Item full>
158 <Grid.Item full>
159 <Flex align="center">
159 <Flex align="center">
160 <Flex.Item>
160 <Flex.Item>
161− {acceptedInvites.getOrElse(0) + _pendingInvites} /{' '}
161+ {acceptedInvites.getOrElse(0)} / {_totalInvites}
162− {_totalInvites}
163 </Flex.Item>
162 </Flex.Item>
164 {_pendingInvites > 0 && (
163 {_pendingInvites > 0 && (
165− <Flex.Item as={Chip} color="yellow">
164+ <Flex.Item as={Chip} className="bg-yellow1 yellow4">
166 {_pendingInvites} pending
165 {_pendingInvites} pending
167 </Flex.Item>
166 </Flex.Item>
168 )}
167 )}
@@ -199,12 +198,14 @@ export default function Point() {
199 <Grid.Item
198 <Grid.Item
200 full
199 full
201 solid
200 solid
202− as={ForwardButton}
201+ as={Button}
202+ center
203 onClick={() => setShowInviteForm(true)}>
203 onClick={() => setShowInviteForm(true)}>
204 Add Members
204 Add Members
205 </Grid.Item>
205 </Grid.Item>
206 )}
206 )}
207 {showInviteForm && <Inviter />}
207 {showInviteForm && <Inviter />}
208+ <Grid.Item full className="mb2" />
208 </>
209 </>
209 )}
210 )}
210 {!loadedInvites && isPlanet && (
211 {!loadedInvites && isPlanet && (