on master help sign in
feat: update Point view, implement Footer w/ portals, convert to as={} pattern
3e1a340Matt Condon (shrugs)
src/components/Footer.jsnew file+17
@@ -0,0 +1,17 @@
1+import React, { Children } from 'react';
2+import { createPortal } from 'react-dom';
3+
4+const kFooterPortalId = 'footer-portal';
5+
6+function Footer({ children }) {
7+ return createPortal(
8+ Children.only(children),
9+ document.getElementById(kFooterPortalId)
10+ );
11+}
12+
13+Footer.Portal = function FooterPortal(props) {
14+ return <div {...props} id={kFooterPortalId} />;
15+};
16+
17+export default Footer;
src/components/InputSigil.js+5−14
@@ -1,4 +1,5 @@
1import React from 'react';
1import React from 'react';
2+import cn from 'classnames';
2import * as ob from 'urbit-ob';
3import * as ob from 'urbit-ob';
3
4
4import Sigil from './Sigil';
5import Sigil from './Sigil';
@@ -31,19 +32,9 @@ export default function ValidatedSigil({
31 const colorway = selectColorway(pass, error, focused);
32 const colorway = selectColorway(pass, error, focused);
32 const valid = ob.isValidPatp(patp);
33 const valid = ob.isValidPatp(patp);
33
34
34− return (
35+ return valid ? (
35− <div className={className}>
36− {valid ? (
37− <Sigil patp={patp} size={size} colorway={colorway} {...rest} />
36+ <Sigil patp={patp} size={size} colorway={colorway} margin={0} {...rest} />
38− ) : (
37+ ) : (
39− <div
40− className="bg-transparent"
38+ <div className={cn('bg-transparent', className)} />
41− style={{
42− width: size,
43− height: size,
44− }}
45− />
46− )}
47− </div>
48 );
39 );
49}
40}
src/components/Inputs.js+1
@@ -78,6 +78,7 @@ export function PointInput(props) {
78 <Input
78 <Input
79 type="text"
79 type="text"
80 placeholder="e.g. ~zod"
80 placeholder="e.g. ~zod"
81+ initialValue={props.initialValue || '~'}
81 validators={[validatePoint, validateNotEmpty]}
82 validators={[validatePoint, validateNotEmpty]}
82 transformers={[prependSig]}
83 transformers={[prependSig]}
83 mono
84 mono
src/components/Passport.jsnew file+25
@@ -0,0 +1,25 @@
1+import React from 'react';
2+import * as ob from 'urbit-ob';
3+import { Grid, Flex } from 'indigo-react';
4+
5+import Sigil from './Sigil';
6+
7+export default function Passport({ point }) {
8+ const name = ob.patp(point);
9+ const birthDate = '2019.1.27';
10+
11+ return (
12+ <Grid gap={16} className="bg-black r8 p7">
13+ <Grid.Item cols={[1, 4]} className="bg-green">
14+ <Sigil patp={name} size={64} margin={0} />
15+ </Grid.Item>
16+ <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
17+ <Flex.Item className="mono white f5">{name}</Flex.Item>
18+ <Flex.Item as={Flex} col>
19+ <Flex.Item className="mono gray4 f6 uppercase">Birth Time</Flex.Item>
20+ <Flex.Item className="mono gray4 f6">{birthDate}</Flex.Item>
21+ </Flex.Item>
22+ </Grid.Item>
23+ </Grid>
24+ );
25+}
src/components/ReactSVGComponents.js+1−2
@@ -7,8 +7,7 @@ const ReactSVGComponents = {
7 return (
7 return (
8 <svg
8 <svg
9 {...attrs}
9 {...attrs}
10− width="100%"
11− height="100%"
10+ style={{ width: '100%', height: '100%' }}
12 version={'1.1'}
11 version={'1.1'}
13 xmlns={'http://www.w3.org/2000/svg'}
12 xmlns={'http://www.w3.org/2000/svg'}
14 key={key}
13 key={key}
src/components/View.js+24−4
@@ -1,13 +1,33 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3+import { Flex } from 'indigo-react';
4+
5+import Footer from './Footer';
3
6
4// View is a top-level component that all Views must render to inherit styling
7// View is a top-level component that all Views must render to inherit styling
5−function View({ className, ...rest }) {
8+function View({ className, children, full = false, ...rest }) {
9+ return (
10+ <Flex
11+ col
12+ justify="between"
6− return <div className={cn('ph5 mw1', className)} {...rest} />;
13+ className={cn(
14+ 'minh-100 ph5',
15+ {
16+ mw1: !full,
17+ 'mw2 ph9-md ph10-lg': full,
18+ },
19+ className
20+ )}
21+ {...rest}>
22+ <Flex.Item className="pv5">{children}</Flex.Item>
23+ <Footer.Portal />
24+ </Flex>
25+ );
7}
26}
8
27
28+// View.Full is sugar for <View full /> to help with readability
9−View.Full = function FullView({ className, ...rest }) {
29+View.Full = function FullView(props) {
10− return <div className={cn('mw2 ph5 ph9-md ph10-lg', className)} {...rest} />;
30+ return <View full {...props} />;
11};
31};
12
32
13export default View;
33export default View;
src/components/old/advancedInput.js+5−5
@@ -1,7 +1,7 @@
1import React, { Component } from 'react';
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
2import PropTypes from 'prop-types';
3
3
4−import { compose, defaultTo, t, f } from '../../lib/lib';
4+import { compose, defaultTo } from '../../lib/lib';
5
5
6// =============================================================================
6// =============================================================================
7// Utils
7// Utils
@@ -14,10 +14,10 @@ const initMessage = data => ({
14
14
15// Validation display triggers
15// Validation display triggers
16const displayTriggers = {
16const displayTriggers = {
17− eagerSuccess: r => t(r.pass),
17+ eagerSuccess: r => r.pass,
18− eagerFailure: r => f(r.pass),
18+ eagerFailure: r => !r.pass,
19− tactfulSuccess: r => t(r.pass) && t(r.beenFocussed),
19+ tactfulSuccess: r => r.pass && r.beenFocussed,
20− tactfulFailure: r => f(r.pass) && t(r.beenFocussed) && f(r.focussed),
20+ tactfulFailure: r => !r.pass && r.beenFocussed && !r.focussed,
21};
21};
22
22
23// =============================================================================
23// =============================================================================
src/indigo-react/components/Button.js+25−16
@@ -1,23 +1,32 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3+import Grid from './Grid';
3
4
4export default function Button({
5export default function Button({
5 solid = false,
6 solid = false,
6 disabled = false,
7 disabled = false,
8+ detail,
7 className,
9 className,
8 icon,
10 icon,
9 children,
11 children,
10 ...rest
12 ...rest
11}) {
13}) {
14+ const textColor = {
15+ white: solid,
16+ black: !solid && !disabled,
17+ gray4: !solid && disabled,
18+ };
12 return (
19 return (
20+ <Grid
13− <a
21+ as="a"
22+ gap={4}
14 className={cn(
23 className={cn(
15− 'pointer pv3 pl4 pr0 truncate flex-row justify-between',
24+ 'pointer p4 truncate flex-row justify-between',
16 {
25 {
17− 'white bg-black bg-gray6-hover': solid && !disabled,
26+ 'bg-black bg-gray6-hover': solid && !disabled,
18− 'white bg-gray3': solid && disabled,
27+ 'bg-gray3': solid && disabled,
19− 'black bg-transparent bg-gray1-hover': !solid && !disabled,
28+ 'bg-transparent bg-gray1-hover': !solid && !disabled,
20− 'gray4 bg-transparent': !solid && disabled,
29+ 'bg-transparent': !solid && disabled,
21 },
30 },
22 className
31 className
23 )}
32 )}
@@ -25,15 +34,15 @@ export default function Button({
25 ...(disabled && { pointerEvents: 'none', cursor: 'not-allowed' }),
34 ...(disabled && { pointerEvents: 'none', cursor: 'not-allowed' }),
26 }}
35 }}
27 {...rest}>
36 {...rest}>
28− {children}
37+ <Grid.Item className="flex-row justify-between" full>
29− <div
38+ <span className={cn(textColor)}>{children}</span>
30− className={cn('ph4', {
39+ <div className={cn('pl4', textColor)}>{icon}</div>
31− white: solid,
40+ </Grid.Item>
32− black: !solid && !disabled,
41+ {detail && (
33− gray4: !solid && disabled,
42+ <Grid.Item full>
34− })}>
43+ <span className="gray4 f6">{detail}</span>
35− {icon}
44+ </Grid.Item>
36− </div>
45+ )}
37− </a>
46+ </Grid>
38 );
47 );
39}
48}
src/indigo-react/components/IndigoApp.js+7−1
@@ -2,5 +2,11 @@ import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3
3
4export default function({ debug = false, ...rest }) {
4export default function({ debug = false, ...rest }) {
5+ return (
6+ <main
5− return <main className={cn({ debug })} {...rest} />;
7+ className={cn('minh-100', { debug })}
8+ style={{ position: 'relative' }}
9+ {...rest}
10+ />
11+ );
6}
12}
src/indigo-react/index.js+2
@@ -1,3 +1,5 @@
1export { default as IndigoApp } from './components/IndigoApp';
1export { default as IndigoApp } from './components/IndigoApp';
2+export { default as Grid } from './components/Grid';
3+export { default as Flex } from './components/Flex';
2export { default as Input } from './components/Input';
4export { default as Input } from './components/Input';
3export { default as Button } from './components/Button';
5export { default as Button } from './components/Button';
src/lib/lib.js−7
@@ -15,11 +15,6 @@ const seq = n => Array.from(Array(n), (_, i) => i);
15
15
16const fill = (n, v) => Array.from(Array(n), () => v);
16const fill = (n, v) => Array.from(Array(n), () => v);
17
17
18−// Terse true/false checks
19−const t = v => v === true;
20−
21−const f = v => v === false;
22−
23const isValidGalaxy = name => {
18const isValidGalaxy = name => {
24 let point;
19 let point;
25 try {
20 try {
@@ -47,8 +42,6 @@ export {
47 defaultTo,
42 defaultTo,
48 seq,
43 seq,
49 fill,
44 fill,
50− t,
51− f,
52 isValidGalaxy,
45 isValidGalaxy,
53 randomPatq,
46 randomPatq,
54};
47};
src/lib/useInvites.jsnew file+34
@@ -0,0 +1,34 @@
1+import { useCallback, useState } from 'react';
2+import Maybe from 'folktale/maybe';
3+import * as azimuth from 'azimuth-js';
4+
5+import { useNetwork } from 'store/network';
6+import useLifecycle from './useLifecycle';
7+
8+// fetch the invites available for a point
9+export default function useInvites(point) {
10+ const { contracts } = useNetwork();
11+
12+ const [invites, _setInvites] = useState(Maybe.Nothing());
13+
14+ const fetchInvites = useCallback(() => {
15+ const _contracts = contracts.getOrElse(null);
16+
17+ if (!contracts) {
18+ return;
19+ }
20+
21+ (async () => {
22+ const count = await azimuth.delegatedSending.getTotalUsableInvites(
23+ _contracts,
24+ point
25+ );
26+ _setInvites(Maybe.Just(count));
27+ })();
28+ }, [_setInvites, point, contracts]);
29+
30+ // fetch invites on first render
31+ useLifecycle(fetchInvites);
32+
33+ return { invites, fetchInvites };
34+}
src/lib/useSyncPoint.jsnew file+12
@@ -0,0 +1,12 @@
1+import { useEffect } from 'react';
2+
3+import { usePointCache } from 'store/pointCache';
4+
5+// sync a specified point to the cache
6+export default function useSyncPoint(point) {
7+ const { fetchPoint } = usePointCache();
8+
9+ useEffect(() => {
10+ fetchPoint(point);
11+ }, [fetchPoint, point]);
12+}
src/store/Provider.js+1−1
@@ -12,10 +12,10 @@ import { TxnCursorProvider } from './txnCursor';
12// nest all of the providers within each other to avoid hella depth
12// nest all of the providers within each other to avoid hella depth
13export default nest([
13export default nest([
14 HistoryProvider,
14 HistoryProvider,
15− TxnConfirmationsProvider,
16 OnlineProvider,
15 OnlineProvider,
17 NetworkProvider,
16 NetworkProvider,
18 WalletProvider,
17 WalletProvider,
18+ TxnConfirmationsProvider,
19 PointCursorProvider,
19 PointCursorProvider,
20 PointCacheProvider,
20 PointCacheProvider,
21 TxnCursorProvider,
21 TxnCursorProvider,
src/store/pointCache.js+29−3
@@ -1,16 +1,42 @@
1−import React, { createContext, forwardRef, useContext, useState } from 'react';
1+import React, {
2+ createContext,
3+ forwardRef,
4+ useCallback,
5+ useContext,
6+ useState,
7+} from 'react';
8+import * as azimuth from 'azimuth-js';
9+
10+import { useNetwork } from './network';
2
11
3export const PointCacheContext = createContext(null);
12export const PointCacheContext = createContext(null);
4
13
5function _usePointCache() {
14function _usePointCache() {
15+ const { contracts } = useNetwork();
6 const [pointCache, _setPointCache] = useState({});
16 const [pointCache, _setPointCache] = useState({});
7
17
8− const addToPointCache = entry =>
18+ const addToPointCache = useCallback(
9− _setPointCache(cache => ({ ...cache, ...entry }));
19+ entry => _setPointCache(cache => ({ ...cache, ...entry })),
20+ [_setPointCache]
21+ );
22+
23+ const fetchPoint = useCallback(
24+ async point => {
25+ const _contracts = contracts.getOrElse(null);
26+ if (!_contracts) {
27+ return;
28+ }
29+
30+ const details = await azimuth.azimuth.getPoint(_contracts, point);
31+ addToPointCache({ [point]: details });
32+ },
33+ [contracts, addToPointCache]
34+ );
10
35
11 return {
36 return {
12 pointCache,
37 pointCache,
13 addToPointCache,
38 addToPointCache,
39+ fetchPoint,
14 };
40 };
15}
41}
16
42
src/style/bridge.scss+13
@@ -13,3 +13,16 @@ input:-webkit-autofill {
13 -webkit-animation-name: autofill;
13 -webkit-animation-name: autofill;
14 -webkit-animation-fill-mode: both;
14 -webkit-animation-fill-mode: both;
15}
15}
16+
17+// min-height the top elements so footer behavior works correctly
18+html {
19+ height: 100%;
20+}
21+
22+body {
23+ min-height: 100%;
24+}
25+
26+#root {
27+ min-height: 100vh;
28+}
src/style/indigo-proposals.scss+6−2
@@ -20,6 +20,10 @@
20 background-color: #333333;
20 background-color: #333333;
21}
21}
22
22
23−.bg-transparent {
23+.clickable {
24− background-color: transparent;
24+ cursor: pointer;
25+}
26+
27+.minh-100 {
28+ min-height: 100vh;
25}
29}
src/views/InviteTicket.js+1−1
@@ -31,7 +31,7 @@ import {
31import { generateWallet, startTransactions } from '../lib/invite';
31import { generateWallet, startTransactions } from '../lib/invite';
32import { withNetwork } from '../store/network';
32import { withNetwork } from '../store/network';
33import { withWallet } from '../store/wallet';
33import { withWallet } from '../store/wallet';
34−import Grid from 'components/Grid';
34+import { Grid } from 'indigo-react';
35
35
36class InviteTicket extends React.Component {
36class InviteTicket extends React.Component {
37 constructor(props) {
37 constructor(props) {
src/views/Point.js+59−140
@@ -1,143 +1,62 @@
1−import { Just, Nothing } from 'folktale/maybe';
1+import React, { useCallback } from 'react';
2−import React from 'react';
3−import { pour } from 'sigil-js';
4−import * as ob from 'urbit-ob';
5−import * as azimuth from 'azimuth-js';
6−import * as need from '../lib/need';
2+import * as need from 'lib/need';
7
3
8−import PointList from '../components/old/PointList';
4+import { usePointCursor } from 'store/pointCursor';
9−import ReactSVGComponents from '../components/ReactSVGComponents';
5+// import { usePointCache } from 'store/pointCache';
10−import KeysAndMetadata from './Point/KeysAndMetadata';
11−import Actions from './Point/Actions';
12−import { H1, H3 } from '../components/old/Base';
13−import { withHistory } from '../store/history';
14−import { withNetwork } from '../store/network';
15−import { compose } from '../lib/lib';
16−import { withWallet } from '../store/wallet';
17−import { withPointCursor } from '../store/pointCursor';
18−import { withPointCache } from '../store/pointCache';
19−import View from 'components/View';
20−
21−class Point extends React.Component {
22− constructor(props) {
23− super(props);
24−
25− this.state = {
26− spawned: [],
27− invites: Nothing(),
28− };
29− }
30−
31− componentDidMount() {
32− this.cachePoints();
33− }
34−
35− componentDidUpdate(prevProps) {
36− //
37− // NB (jtobin)
38− //
39− // When a link in the 'issued points' component is clicked, we don't
40− // actually leave the Point component, and componentDidMount will not fire
41− // again. But, we can force a call to cachePoints here.
42− //
43− const oldCursor = prevProps.pointCursor.getOrElse(true);
44− const newCursor = this.props.pointCursor.getOrElse(true);
45−
46− if (oldCursor !== newCursor) {
47− this.cachePoints();
48− }
49− }
50−
51− cachePoints() {
52− const { web3, contracts, pointCursor, addToPointCache } = this.props;
53−
54− web3.chain(_ =>
55− contracts.chain(ctrcs =>
56− pointCursor.chain(point => {
57− azimuth.azimuth
58− .getPoint(ctrcs, point)
59− .then(details => addToPointCache({ [point]: details }));
60− azimuth.delegatedSending
61− .getTotalUsableInvites(ctrcs, point)
62− .then(count => this.setState({ invites: Just(count) }));
63− this.updateSpawned(ctrcs, point);
64− })
65− )
66− );
67− }
68−
69− updateSpawned = contracts => {
70− const { pointCursor } = this.props;
71−
72− pointCursor.matchWith({
73− Nothing: _ => {},
74− Just: point => {
75− azimuth.azimuth
76− .getSpawned(contracts, point.value)
77− .then(spawned => this.setState({ spawned }));
78− },
79− });
80− };
81−
82− render() {
83− const { web3, history, wallet, pointCache } = this.props;
84−
85− const { spawned } = this.state;
86
6
87− const point = need.pointCursor(this.props.pointCursor);
7+import View from 'components/View';
88−
8+import useInvites from 'lib/useInvites';
89− const pointDetails =
9+import useSyncPoint from 'lib/useSyncPoint';
90− point in pointCache ? Just(pointCache[point]) : Nothing();
10+import Passport from 'components/Passport';
91−
11+import { Grid } from 'indigo-react';
92− const name = ob.patp(point);
12+import { ForwardButton } from 'components/Buttons';
93−
13+import { useHistory } from 'store/history';
94− const sigil = pour({
14+import { ROUTE_NAMES } from 'lib/routeNames';
95− patp: name,
15+import Footer from 'components/Footer';
96− renderer: ReactSVGComponents,
16+
97− size: 256,
17+export default function Point() {
98− });
18+ const history = useHistory();
99−
19+ const { pointCursor } = usePointCursor();
100− const online = Just.hasInstance(web3);
20+ // const { pointCache } = usePointCache();
101−
21+
102− const authenticated = Just.hasInstance(wallet);
22+ const point = need.pointCursor(pointCursor);
103−
23+
104− const issuedPointList =
24+ // fetch the invites for the current cursor
105− spawned.length === 0 ? (
25+ const { invites } = useInvites(point);
106− <div />
26+
107− ) : (
27+ const goAdmin = useCallback(() => {}, []);
108− <div>
28+ // ^ TODO: push .ADMIN
109− <H3>{'Issued Points'}</H3>
29+ const goInvite = useCallback(() => history.push(ROUTE_NAMES.INVITES_MANAGE), [
110−
30+ history,
111− <PointList routeHandler={history.popAndPush} points={spawned} />
31+ ]);
112− </div>
32+
113− );
33+ // sync the current cursor
114−
34+ useSyncPoint(point);
35+
36+ // const pointDetails = need.fromPointCache(pointCache, point);
37+
115− return (
38+ return (
116− <View>
39+ <View>
117− <div className={'mt-12 pt-6'}>{sigil}</div>
40+ <Passport point={point} />
118− <H1>
41+ <Grid className="pt2">
119− <code>{name}</code>
42+ <Grid.Item full>
120− </H1>
43+ <ForwardButton onClick={goAdmin}>Admin</ForwardButton>
121− {authenticated ? (
44+ </Grid.Item>
122− <Actions
45+ <Grid.Divider />
123− online={online}
46+ <Grid.Item full>
124− point={point}
47+ <ForwardButton detail="Boot your computer" disabled>
125− pointDetails={pointDetails}
48+ Boot Arvo
126− invites={this.state.invites}
49+ </ForwardButton>
127− />
50+ </Grid.Item>
128− ) : null}
51+ </Grid>
129−
52+ <Footer>
130− {online ? <KeysAndMetadata pointDetails={pointDetails} /> : <div />}
53+ <Grid className="pt2">
131− {issuedPointList}
54+ <Grid.Divider />
132− </View>
55+ <Grid.Item full>
133− );
56+ <ForwardButton onClick={goInvite}>Invite</ForwardButton>
134− }
57+ </Grid.Item>
58+ </Grid>
59+ </Footer>
60+ </View>
61+ );
135}
62}
136−
137−export default compose(
138− withNetwork,
139− withHistory,
140− withWallet,
141− withPointCursor,
142− withPointCache
143−)(Point);
src/views/Point/Actions.js+1−1
@@ -8,7 +8,7 @@ import { Button } from '../../components/old/Base';
8import { ROUTE_NAMES } from '../../lib/routeNames';
8import { ROUTE_NAMES } from '../../lib/routeNames';
9import { useHistory } from '../../store/history';
9import { useHistory } from '../../store/history';
10import { useWallet } from '../../store/wallet';
10import { useWallet } from '../../store/wallet';
11−import Grid from 'components/Grid';
11+import { Grid } from 'indigo-react';
12
12
13const isPlanet = point =>
13const isPlanet = point =>
14 azimuth.getPointSize(point) === azimuth.PointSize.Planet;
14 azimuth.getPointSize(point) === azimuth.PointSize.Planet;
src/views/Point/KeysAndMetadata.js+1−1
@@ -2,7 +2,7 @@ import React from 'react';
2import { H2, H4 } from '../../components/old/Base';
2import { H2, H4 } from '../../components/old/Base';
3
3
4import { ETH_ZERO_ADDR, eqAddr } from '../../lib/wallet';
4import { ETH_ZERO_ADDR, eqAddr } from '../../lib/wallet';
5−import Grid from 'components/Grid';
5+import { Grid } from 'indigo-react';
6
6
7const NULL_KEY =
7const NULL_KEY =
8 '0x0000000000000000000000000000000000000000000000000000000000000000';
8 '0x0000000000000000000000000000000000000000000000000000000000000000';
src/views/Points.js+1−1
@@ -13,7 +13,7 @@ import { compose } from '../lib/lib';
13import { withWallet } from '../store/wallet';
13import { withWallet } from '../store/wallet';
14import { withPointCache } from '../store/pointCache';
14import { withPointCache } from '../store/pointCache';
15import View from 'components/View';
15import View from 'components/View';
16−import Grid from 'components/Grid';
16+import { Grid } from 'indigo-react';
17
17
18const hasTransferProxy = (cache, point) =>
18const hasTransferProxy = (cache, point) =>
19 point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
19 point in cache ? !eqAddr(cache[point].transferProxy, ETH_ZERO_ADDR) : false;
src/views/SentTransaction.js+1−1
@@ -12,7 +12,7 @@ import { useTxnConfirmations } from '../store/txnConfirmations';
12import { useNetwork } from '../store/network';
12import { useNetwork } from '../store/network';
13import { useTxnCursor } from '../store/txnCursor';
13import { useTxnCursor } from '../store/txnCursor';
14import View from 'components/View';
14import View from 'components/View';
15−import Grid from 'components/Grid';
15+import { Grid } from 'indigo-react';
16
16
17class Success extends React.Component {
17class Success extends React.Component {
18 constructor(props) {
18 constructor(props) {
src/views/ViewPoint.js+24−18
@@ -1,4 +1,4 @@
1−import React, { useState } from 'react';
1+import React, { useCallback, useState } from 'react';
2import Maybe from 'folktale/maybe';
2import Maybe from 'folktale/maybe';
3import * as ob from 'urbit-ob';
3import * as ob from 'urbit-ob';
4
4
@@ -22,11 +22,18 @@ export default function ViewPoint() {
22 const [error, setError] = useState();
22 const [error, setError] = useState();
23 const [focused, setFocused] = useState(true);
23 const [focused, setFocused] = useState(true);
24
24
25+ const disabled = !pass;
26+ const goForward = useCallback(() => {
27+ setPointCursor(Maybe.Just(parseInt(ob.patp2dec(pointName), 10)));
28+ // ^ pointCursor expects native number type, not string
29+ history.popAndPush(ROUTE_NAMES.POINT);
30+ }, [setPointCursor, history, pointName]);
31+
25 return (
32 return (
26 <View>
33 <View>
27− <H1>{'View a Point'}</H1>
34+ <H1>View a Point</H1>
28
35
29− <P>{'Enter a point name to view its public information.'}</P>
36+ <P>Enter a point name to view its public information.</P>
30
37
31 <PointInput
38 <PointInput
32 name="point"
39 name="point"
@@ -35,25 +42,24 @@ export default function ViewPoint() {
35 onPass={setPass}
42 onPass={setPass}
36 onError={setError}
43 onError={setError}
37 onFocus={setFocused}
44 onFocus={setFocused}
45+ onEnter={goForward}
46+ accessory={
47+ <InputSigil
48+ patp={pointName}
49+ size={68}
50+ margin={8}
51+ pass={pass}
52+ focused={focused}
53+ error={error}
54+ />
55+ }
38 autoFocus
56 autoFocus
39 />
57 />
40
58
41− <InputSigil
42− patp={pointName}
43− size={68}
44− margin={8}
45− pass={pass}
46− focused={focused}
47− error={error}
48− />
49−
50 <ForwardButton
59 <ForwardButton
60+ className="mt3"
51− disabled={!pass}
61+ disabled={disabled}
52− onClick={() => {
62+ onClick={goForward}
53− setPointCursor(Maybe.Just(parseInt(ob.patp2dec(pointName), 10)));
54− // ^ pointCursor expects native number type, not string
55− history.popAndPush(ROUTE_NAMES.POINT);
56− }}
57 solid>
63 solid>
58 Continue
64 Continue
59 </ForwardButton>
65 </ForwardButton>