on master help sign in
feat: implement manage invites for parties view
commitabbbcbc77b89b105706c39fbc424f4224d1c5b66 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateWed 17 Jul 2019, 14:01 (7 years ago) parent75e1000
13 files changed+251−79
src/components/DownloadKeyfileButton.js−7 src/lib/routeNames.js+1 src/lib/router.js+3 src/lib/transformers.js+9−7 src/lib/useCurrentPermissions.js+17 src/lib/useInputs.js+19−1 src/lib/useKeyfileGenerator.js+12−13 src/lib/usePermissionsForPoint.js+15 src/lib/validators.js+8−1 src/views/Admin/AdminEditPermissions.js+9−12 src/views/Admin/AdminHome.js+3−12 src/views/Party/PartySetPoolSize.js+145 src/views/Point.js+10−26
src/components/DownloadKeyfileButton.js−7
@@ -1,10 +1,7 @@
11import React from 'react';
22import { Flex, B } from 'indigo-react';
33
4−import { usePointCursor } from 'store/pointCursor';
5−
64import useKeyfileGenerator from 'lib/useKeyfileGenerator';
7−import * as need from 'lib/need';
85
96import { DownloadButton } from 'components/Buttons';
107
@@ -13,11 +10,7 @@ export default function DownloadKeyfileButton({
1310 className,
1411 ...rest
1512}) {
16− const { pointCursor } = usePointCursor();
17− const _point = need.point(pointCursor);
18−
1913 const { generating, available, downloaded, download } = useKeyfileGenerator(
20− _point,
2114 networkSeed
2215 );
2316
src/lib/routeNames.js+1
@@ -28,4 +28,5 @@ export const ROUTE_NAMES = {
2828 TRANSFER: Symbol('TRANSFER'),
2929 SENT_TRANSACTION: Symbol('SENT_TRANSACTION'),
3030 GEN_KEYFILE: Symbol('GEN_KEYFILE'),
31+ PARTY_SET_POOL_SIZE: Symbol('PARTY_SET_POOL_SIZE'),
3132};
src/lib/router.js+3
@@ -22,6 +22,7 @@ import Invite from 'views/Invite';
2222import Activate from 'views/Activate';
2323
2424import { ROUTE_NAMES } from './routeNames';
25+import PartySetPoolSize from 'views/Party/PartySetPoolSize';
2526
2627export const ROUTES = {
2728 [ROUTE_NAMES.LANDING]: Landing,
@@ -44,4 +45,6 @@ export const ROUTES = {
4445 [ROUTE_NAMES.SET_KEYS]: SetKeys,
4546 [ROUTE_NAMES.TRANSFER]: Transfer,
4647 [ROUTE_NAMES.SENT_TRANSACTION]: SentTransaction,
48+ [ROUTE_NAMES.PARTY_SET_POOL_SIZE]: PartySetPoolSize,
49+ // TODO: nest this route under the Party.js router
4750};
src/lib/transformers.js+9−7
@@ -1,6 +1,14 @@
11// import { fill } from './lib'
22
3−const prependSig = s => (s.charAt(0) !== '~' ? `~${s}` : s);
3+export const prependSig = s => (s.charAt(0) !== '~' ? `~${s}` : s);
4+
5+export const convertToNumber = s => {
6+ try {
7+ return parseInt(s, 10);
8+ } catch {
9+ return 0;
10+ }
11+};
412
513// const hideAllButLast = s => {
614// const ll = s[s.length -1];
@@ -9,9 +17,3 @@ const prependSig = s => (s.charAt(0) !== '~' ? `~${s}` : s);
917// };
1018//
1119// const hideAll = s => fill(s.length, '•');
12−
13−export {
14− prependSig,
15− // hideAllButLast,
16− // hideAll,
17−};
src/lib/useCurrentPermissions.jsnew file+17
@@ -0,0 +1,17 @@
1+import usePermissionsForPoint from './usePermissionsForPoint';
2+import { useWallet } from 'store/wallet';
3+import { usePointCursor } from 'store/pointCursor';
4+
5+export default function useCurrentPermissions() {
6+ const { wallet } = useWallet();
7+ const { pointCursor } = usePointCursor();
8+
9+ const _address = wallet.matchWith({
10+ Nothing: () => '',
11+ Just: p => p.value.address,
12+ });
13+
14+ const _point = pointCursor.getOrElse(null);
15+
16+ return usePermissionsForPoint(_address, _point);
17+}
src/lib/useInputs.js+19−1
@@ -16,8 +16,9 @@ import {
1616 validateMaximumPatpByteLength,
1717 validateEthereumAddress,
1818 validateNotNullAddress,
19+ validateGreaterThan,
1920} from 'lib/validators';
20−import { prependSig } from 'lib/transformers';
21+import { prependSig, convertToNumber } from 'lib/transformers';
2122
2223import InputSigil from 'components/InputSigil';
2324
@@ -232,6 +233,23 @@ export function useAddressInput({ ...rest }) {
232233 );
233234}
234235
236+const kNumberTransformers = [convertToNumber];
237+const kNumberValidators = [validateGreaterThan(0)];
238+export function useNumberInput({ ...rest }) {
239+ return useFirstOf(
240+ useForm([
241+ {
242+ type: 'number',
243+ label: 'Number',
244+ autoComplete: 'off',
245+ transformers: kNumberTransformers,
246+ validators: kNumberValidators,
247+ ...rest,
248+ },
249+ ])
250+ );
251+}
252+
235253const kEmailValidators = [validateEmail, validateNotEmpty];
236254export const buildEmailInputConfig = extra => ({
237255 type: 'email',
src/lib/useKeyfileGenerator.js+12−13
@@ -14,24 +14,23 @@ import {
1414 deriveNetworkKeys,
1515 compileNetworkingKey,
1616} from './keys';
17−import usePermissionsForPoint from './usePermissionsForPoint';
17+import useCurrentPermissions from './useCurrentPermissions';
18+import { usePointCursor } from 'store/pointCursor';
1819
19−export default function useKeyfileGenerator(point, manualNetworkSeed) {
20+export default function useKeyfileGenerator(manualNetworkSeed) {
2021 const { getDetails } = usePointCache();
2122 const { urbitWallet, wallet, authMnemonic } = useWallet();
23+ const { pointCursor } = usePointCursor();
2224
2325 const [downloaded, setDownloaded] = useState(false);
2426 const [generating, setGenerating] = useState(true);
2527 const [keyfile, setKeyfile] = useState(false);
2628
27− const _address = need.wallet(wallet).address;
28− const _details = need.details(getDetails(point));
29+ const _point = need.point(pointCursor);
30+ const _details = need.details(getDetails(_point));
2931
3032 const networkRevision = parseInt(_details.keyRevisionNumber, 10);
31− const { isOwner, isManagementProxy } = usePermissionsForPoint(
32− _address,
33− point
34− );
33+ const { isOwner, isManagementProxy } = useCurrentPermissions();
3534
3635 const hasNetworkingKeys = networkRevision > 0;
3736 const available =
@@ -68,16 +67,16 @@ export default function useKeyfileGenerator(point, manualNetworkSeed) {
6867 return;
6968 }
7069
71− setKeyfile(compileNetworkingKey(pair, point, networkRevision));
70+ setKeyfile(compileNetworkingKey(pair, _point, networkRevision));
7271 setGenerating(false);
7372 }, [
73+ networkRevision,
7474 manualNetworkSeed,
7575 urbitWallet,
7676 wallet,
7777 authMnemonic,
7878 _details,
79− networkRevision,
80− point,
79+ _point,
8180 ]);
8281
8382 const download = useCallback(() => {
@@ -85,11 +84,11 @@ export default function useKeyfileGenerator(point, manualNetworkSeed) {
8584 new Blob([keyfile], {
8685 type: 'text/plain;charset=utf-8',
8786 }),
88− `${ob.patp(point).slice(1)}-${networkRevision}.key`
87+ `${ob.patp(_point).slice(1)}-${networkRevision}.key`
8988 // TODO: ^ unifiy "remove tilde" calls
9089 );
9190 setDownloaded(true);
92− }, [keyfile, point, networkRevision]);
91+ }, [_point, keyfile, networkRevision]);
9392
9493 useEffect(() => {
9594 generate();
src/lib/usePermissionsForPoint.js+15
@@ -1,6 +1,9 @@
11import { azimuth } from 'azimuth-js';
22
33import { usePointCache } from 'store/pointCache';
4+import { useNetwork } from 'store/network';
5+
6+import * as need from 'lib/need';
47
58import { eqAddr, ETH_ZERO_ADDR } from './wallet';
69
@@ -23,6 +26,8 @@ const NULL_PERMISSIONS = {
2326 canTransfer: false,
2427 canSpawn: false,
2528 canVote: false,
29+ //
30+ spawnIsDelegatedSending: false,
2631};
2732
2833/**
@@ -31,10 +36,13 @@ const NULL_PERMISSIONS = {
3136 */
3237export default function usePermissionsForPoint(address, point) {
3338 const { getDetails } = usePointCache();
39+ const { contracts } = useNetwork();
3440
3541 return getDetails(point).matchWith({
3642 Nothing: () => NULL_PERMISSIONS,
3743 Just: ({ value: details }) => {
44+ const _contracts = need.contracts(contracts);
45+
3846 const pointSize = azimuth.getPointSize(point);
3947 const isPlanet = pointSize === azimuth.PointSize.Planet;
4048 const isStar = pointSize === azimuth.PointSize.Star;
@@ -60,6 +68,11 @@ export default function usePermissionsForPoint(address, point) {
6068 const canSpawn = (isStar || isGalaxy) && (isOwner || isSpawnProxy);
6169 const canVote = isGalaxy && (isOwner || isVotingProxy);
6270
71+ const spawnIsDelegatedSending = eqAddr(
72+ _contracts.delegatedSending.address,
73+ details.spawnProxy
74+ );
75+
6376 return {
6477 isPlanet,
6578 isStar,
@@ -79,6 +92,8 @@ export default function usePermissionsForPoint(address, point) {
7992 canTransfer,
8093 canSpawn,
8194 canVote,
95+ //
96+ spawnIsDelegatedSending,
8297 };
8398 },
8499 });
src/lib/validators.js+8−1
@@ -74,7 +74,7 @@ export const validateNotEmpty = m =>
7474 prevMessage: m,
7575 validator: d => {
7676 try {
77− return d.length > 1;
77+ return d.length > 0;
7878 } catch {
7979 return false;
8080 }
@@ -237,6 +237,13 @@ export const validateMaximumLength = l => m =>
237237 error: `Must be ${l} characters or fewer.`,
238238 });
239239
240+export const validateGreaterThan = l => m =>
241+ simpleValidatorWrapper({
242+ prevMessage: m,
243+ validator: d => d > l,
244+ error: `Must be at least ${l}`,
245+ });
246+
240247export const validatePatpByteLength = byteLength => {
241248 return validateLength(patpStringLength(byteLength));
242249};
src/views/Admin/AdminEditPermissions.js+9−12
@@ -1,30 +1,28 @@
11import React, { useCallback } from 'react';
22import { Just, Nothing } from 'folktale/maybe';
3−import * as need from 'lib/need';
43import { Grid } from 'indigo-react';
54import { azimuth } from 'azimuth-js';
65
7−import { ForwardButton } from 'components/Buttons';
8−import { matchBlinkyDate } from 'components/Blinky';
9−
10−import { ETH_ZERO_ADDR } from 'lib/wallet';
11−
126import { useNetwork } from 'store/network';
13−import { useWallet } from 'store/wallet';
147import { usePointCursor } from 'store/pointCursor';
158import { usePointCache } from 'store/pointCache';
16−import ViewHeader from 'components/ViewHeader';
17−import usePermissionsForPoint from 'lib/usePermissionsForPoint';
9+
10+import * as need from 'lib/need';
11+import { ETH_ZERO_ADDR } from 'lib/wallet';
1812import { PROXY_TYPE, proxyTypeToHuman } from 'lib/proxy';
1913import { useLocalRouter } from 'lib/LocalRouter';
2014import capitalize from 'lib/capitalize';
2115import { eqAddr } from 'lib/wallet';
16+import useCurrentPermissions from 'lib/useCurrentPermissions';
17+
18+import ViewHeader from 'components/ViewHeader';
19+import { ForwardButton } from 'components/Buttons';
20+import { matchBlinkyDate } from 'components/Blinky';
2221import MiniBackButton from 'components/MiniBackButton';
2322
2423export default function AdminEditPermissions() {
2524 const { pop, push, names } = useLocalRouter();
2625 const { contracts } = useNetwork();
27− const { wallet } = useWallet();
2826 const { pointCursor } = usePointCursor();
2927 const { getDetails, getRekeyDate } = usePointCache();
3028
@@ -34,11 +32,10 @@ export default function AdminEditPermissions() {
3432 const pointSize = azimuth.getPointSize(point);
3533 const isParent = pointSize !== azimuth.PointSize.Planet;
3634 const isSenate = pointSize === azimuth.PointSize.Galaxy;
37− const address = need.wallet(wallet).address;
3835
3936 const details = need.details(getDetails(point));
4037 const networkRevision = parseInt(details.keyRevisionNumber, 10);
41− const { canManage, isOwner } = usePermissionsForPoint(address, point);
38+ const { canManage, isOwner } = useCurrentPermissions();
4239
4340 const goSetProxy = useCallback(
4441 proxyType => push(names.SET_PROXY, { proxyType }),
src/views/Admin/AdminHome.js+3−12
@@ -1,31 +1,22 @@
11import React, { useCallback } from 'react';
22import { Just } from 'folktale/maybe';
33import { Grid, B } from 'indigo-react';
4−import * as need from 'lib/need';
54
65import { ForwardButton } from 'components/Buttons';
76
87import { useWallet } from 'store/wallet';
9−import { usePointCursor } from 'store/pointCursor';
10−
11−import usePermissionsForPoint from 'lib/usePermissionsForPoint';
128
139import ViewHeader from 'components/ViewHeader';
1410import { useLocalRouter } from 'lib/LocalRouter';
1511import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
1612import MiniBackButton from 'components/MiniBackButton';
13+import useCurrentPermissions from 'lib/useCurrentPermissions';
1714
1815export default function AdminHome() {
1916 const { push, names, pop } = useLocalRouter();
20− const { urbitWallet, wallet } = useWallet();
21− const { pointCursor } = usePointCursor();
17+ const { urbitWallet } = useWallet();
2218
23− const point = need.point(pointCursor);
24− const address = need.addressFromWallet(wallet);
25− const { isOwner, canTransfer, isTransferProxySet } = usePermissionsForPoint(
26− address,
27− point
28− );
19+ const { isOwner, canTransfer, isTransferProxySet } = useCurrentPermissions();
2920
3021 const goRedownload = useCallback(() => push(names.REDOWNLOAD), [push, names]);
3122 const goReticket = useCallback(() => push(names.RETICKET), [push, names]);
src/views/Party/PartySetPoolSize.jsnew file+145
@@ -0,0 +1,145 @@
1+import React, { useCallback, useEffect } from 'react';
2+import cn from 'classnames';
3+import { Grid, Text, Input } from 'indigo-react';
4+import * as azimuth from 'azimuth-js';
5+
6+import { useNetwork } from 'store/network';
7+import { usePointCursor } from 'store/pointCursor';
8+import { usePointCache } from 'store/pointCache';
9+
10+import * as need from 'lib/need';
11+import { usePointInput, useNumberInput } from 'lib/useInputs';
12+import useEthereumTransaction from 'lib/useEthereumTransaction';
13+import { GAS_LIMITS } from 'lib/constants';
14+
15+import ViewHeader from 'components/ViewHeader';
16+import MiniBackButton from 'components/MiniBackButton';
17+import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18+import patp2dec from 'lib/patp2dec';
19+import { useHistory } from 'store/history';
20+import View from 'components/View';
21+import useCurrentPermissions from 'lib/useCurrentPermissions';
22+import WarningBox from 'components/WarningBox';
23+
24+function useSetPoolSize() {
25+ const { contracts } = useNetwork();
26+ const { pointCursor } = usePointCursor();
27+ const { syncOwnedPoint } = usePointCache();
28+
29+ const _contracts = need.contracts(contracts);
30+ const _point = need.point(pointCursor);
31+
32+ const {
33+ construct: _construct,
34+ confirmed,
35+ inputsLocked,
36+ bind,
37+ } = useEthereumTransaction(GAS_LIMITS.DEFAULT);
38+ // TODO: GAS_LIMITS.SET_POOL_SIZE
39+
40+ const construct = useCallback(
41+ (poolOwner, poolSize) =>
42+ _construct(
43+ azimuth.delegatedSending.setPoolSize(
44+ _contracts,
45+ _point,
46+ poolOwner,
47+ poolSize
48+ )
49+ ),
50+ [_construct, _contracts, _point]
51+ );
52+
53+ // sync point details after success
54+ useEffect(() => {
55+ if (confirmed) {
56+ syncOwnedPoint(_point);
57+ }
58+ }, [_point, confirmed, syncOwnedPoint]);
59+
60+ return {
61+ construct,
62+ confirmed,
63+ inputsLocked,
64+ bind,
65+ };
66+}
67+
68+export default function PartySetPoolSize() {
69+ const { pop } = useHistory();
70+ const { contracts } = useNetwork();
71+
72+ const _contracts = need.contracts(contracts);
73+
74+ const { spawnIsDelegatedSending } = useCurrentPermissions();
75+
76+ const { construct, confirmed, inputsLocked, bind } = useSetPoolSize();
77+
78+ const [poolOwnerInput, { pass: validPoint, data: poolOwner }] = usePointInput(
79+ {
80+ name: 'point',
81+ label: 'Point',
82+ disabled: inputsLocked,
83+ }
84+ );
85+
86+ const [
87+ poolSizeInput,
88+ { pass: validPoolSize, data: poolSize },
89+ ] = useNumberInput({
90+ name: 'poolsize',
91+ label: 'Pool Size',
92+ initialValue: 5,
93+ disabled: inputsLocked,
94+ });
95+
96+ useEffect(() => {
97+ if (validPoint && validPoolSize) {
98+ construct(patp2dec(poolOwner), poolSize);
99+ }
100+ }, [poolOwner, construct, validPoint, validPoolSize, poolSize]);
101+
102+ return (
103+ <View inset>
104+ <Grid>
105+ <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
106+ <Grid.Item full as={ViewHeader}>
107+ Set Pool Size
108+ </Grid.Item>
109+ {!spawnIsDelegatedSending && (
110+ <Grid.Item full as={WarningBox} className="mb4">
111+ The spawn proxy must be set to{' '}
112+ <code className="mono f6">
113+ {_contracts.delegatedSending.address}
114+ </code>{' '}
115+ for invitations to be available.
116+ </Grid.Item>
117+ )}
118+ <Grid.Item
119+ full
120+ as={Text}
121+ className={cn('f5', {
122+ green3: confirmed,
123+ })}>
124+ {confirmed
125+ ? `${poolSize} invites have been allocated to ${poolOwner}`
126+ : `Allocate invites to a child point.`}
127+ </Grid.Item>
128+
129+ {!confirmed && (
130+ <>
131+ <Grid.Item full as={Input} {...poolOwnerInput} className="mt4" />
132+ <Grid.Item full as={Input} {...poolSizeInput} className="mb4" />
133+ </>
134+ )}
135+
136+ <Grid.Item
137+ full
138+ as={InlineEthereumTransaction}
139+ {...bind}
140+ onReturn={() => pop()}
141+ />
142+ </Grid>
143+ </View>
144+ );
145+}
src/views/Point.js+10−26
@@ -15,40 +15,27 @@ import { matchBlinky } from 'components/Blinky';
1515
1616import useInvites from 'lib/useInvites';
1717import { useSyncOwnedPoints } from 'lib/useSyncPoints';
18−
19−import Actions from './Point/Actions';
20−import { useWallet } from 'store/wallet';
21−import usePermissionsForPoint from 'lib/usePermissionsForPoint';
18+import useCurrentPermissions from 'lib/useCurrentPermissions';
2219
2320export default function Point() {
2421 const { push, names } = useHistory();
2522 const { pointCursor } = usePointCursor();
26− const { wallet } = useWallet();
2723
2824 const point = need.point(pointCursor);
2925
30− const { isActiveOwner, canManage } = usePermissionsForPoint(
31− // using empty string should be ok here
32− wallet.matchWith({
33− Nothing: () => '',
34− Just: p => p.value.address,
35− }),
36− point
37− );
26+ const { isActiveOwner, canManage } = useCurrentPermissions();
3827
3928 // fetch the invites for the current cursor
4029 const { availableInvites } = useInvites(point);
4130
42− const showActions = Just.hasInstance(wallet);
43−
4431 const goAdmin = useCallback(() => push(names.ADMIN), [push, names]);
4532
4633 const goInvite = useCallback(() => push(names.INVITE), [push, names]);
4734
48− const goParties = useCallback(() => push(names.INVITES_MANAGE), [
49− push,
50− names,
51− ]);
35+ const goPartiesSetPoolSize = useCallback(
36+ () => push(names.PARTY_SET_POOL_SIZE),
37+ [push, names]
38+ );
5239
5340 const inviteButton = (() => {
5441 switch (azimuth.getPointSize(point)) {
@@ -62,8 +49,10 @@ export default function Point() {
6249 //
6350 case azimuth.PointSize.Star:
6451 return (
65− <FooterButton disabled={!isActiveOwner} onClick={goParties}>
66− Manage parties
52+ <FooterButton
53+ disabled={!isActiveOwner}
54+ onClick={goPartiesSetPoolSize}>
55+ Manage Parties
6756 </FooterButton>
6857 );
6958 //
@@ -88,11 +77,6 @@ export default function Point() {
8877 </Grid.Item>
8978 <Grid.Divider />
9079 <Grid.Item full as={BootArvoButton} disabled />
91− {showActions && (
92− <Grid.Item full>
93− <Actions />
94− </Grid.Item>
95− )}
9680 </Grid>
9781
9882 {inviteButton}