on master help sign in
feat: allow nested local routers and build out Invite/URL
6542dceMatt Condon (shrugs)
src/Bridge.js+8−5
@@ -7,6 +7,7 @@ import Router from 'components/Router';
7import Provider from 'store/Provider';
7import Provider from 'store/Provider';
8
8
9import { ROUTE_NAMES } from 'lib/routeNames';
9import { ROUTE_NAMES } from 'lib/routeNames';
10+import { ROUTES } from 'lib/router';
10import { NETWORK_TYPES } from 'lib/network';
11import { NETWORK_TYPES } from 'lib/network';
11import { walletFromMnemonic } from 'lib/wallet';
12import { walletFromMnemonic } from 'lib/wallet';
12import { isDevelopment } from 'lib/flags';
13import { isDevelopment } from 'lib/flags';
@@ -22,12 +23,12 @@ const shouldStubLocal = process.env.REACT_APP_STUB_LOCAL === 'true';
22const kIsStubbed = isDevelopment && shouldStubLocal;
23const kIsStubbed = isDevelopment && shouldStubLocal;
23const kInitialRoutes = kIsStubbed
24const kInitialRoutes = kIsStubbed
24 ? [
25 ? [
25− { name: ROUTE_NAMES.LANDING },
26+ { key: ROUTE_NAMES.LANDING },
26− { name: ROUTE_NAMES.NETWORK },
27+ { key: ROUTE_NAMES.NETWORK },
27− { name: ROUTE_NAMES.WALLET },
28+ { key: ROUTE_NAMES.WALLET },
28− { name: ROUTE_NAMES.POINTS },
29+ { key: ROUTE_NAMES.POINTS },
29 ]
30 ]
30− : [{ name: ROUTE_NAMES.LANDING }];
31+ : [{ key: ROUTE_NAMES.LANDING }];
31
32
32const kInitialWallet = kIsStubbed
33const kInitialWallet = kIsStubbed
33 ? walletFromMnemonic(
34 ? walletFromMnemonic(
@@ -43,6 +44,8 @@ const kInitialPointCursor = kIsStubbed ? Just(0) : Nothing();
43export default function Bridge() {
44export default function Bridge() {
44 return (
45 return (
45 <Provider
46 <Provider
47+ views={ROUTES}
48+ names={ROUTE_NAMES}
46 initialRoutes={kInitialRoutes}
49 initialRoutes={kInitialRoutes}
47 initialNetworkType={kInitialNetworkType}
50 initialNetworkType={kInitialNetworkType}
48 initialWallet={kInitialWallet}
51 initialWallet={kInitialWallet}
src/components/Crumbs.jsnew file+32
@@ -0,0 +1,32 @@
1+import React from 'react';
2+import cn from 'classnames';
3+import { Flex, Breadcrumb } from 'indigo-react';
4+
5+export default function Crumbs({ className, routes = [] }) {
6+ const lastIndex = routes.length - 1;
7+ const textStyle = 'gray4 mono';
8+ return (
9+ <Flex className={className} row wrap>
10+ {routes.map((route, i) => {
11+ const disabled = !route.action;
12+
13+ return (
14+ <React.Fragment key={route.text}>
15+ <Flex.Item
16+ as={Breadcrumb}
17+ onClick={route.action}
18+ disabled={disabled}
19+ className={cn(textStyle, { 'clickable underline': !disabled })}>
20+ {route.text}
21+ </Flex.Item>
22+ {i !== lastIndex && (
23+ <Flex.Item as={Breadcrumb} className={cn(textStyle, 'mh4')}>
24+ /
25+ </Flex.Item>
26+ )}
27+ </React.Fragment>
28+ );
29+ })}
30+ </Flex>
31+ );
32+}
src/components/FooterButton.jsnew file+17
@@ -0,0 +1,17 @@
1+import React from 'react';
2+import { Grid, Button } from 'indigo-react';
3+
4+import Footer from './Footer';
5+
6+export default function FooterButton(props) {
7+ return (
8+ <Footer>
9+ <Grid className="pt2">
10+ <Grid.Divider />
11+ <Grid.Item full>
12+ <Button {...props} />
13+ </Grid.Item>
14+ </Grid>
15+ </Footer>
16+ );
17+}
src/components/Router.js+1−5
@@ -1,13 +1,9 @@
1import React from 'react';
1import React from 'react';
2
2
3import { useHistory } from 'store/history';
3import { useHistory } from 'store/history';
4−import router from 'lib/router';
5
4
6−// router is just a component that renders a specific view
7−// depending on the lastest route in the history
8export default function Router() {
5export default function Router() {
9− const history = useHistory();
6+ const { Route } = useHistory();
10− const Route = router(history.peek());
11
7
12 return <Route />;
8 return <Route />;
13}
9}
src/indigo-react/components/Button.js+11−5
@@ -1,13 +1,16 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3+
3import Grid from './Grid';
4import Grid from './Grid';
5+import Flex from './Flex';
6+import { HelpText } from './Typography';
4
7
5export default function Button({
8export default function Button({
6 solid = false,
9 solid = false,
7 disabled = false,
10 disabled = false,
8 detail,
11 detail,
9 className,
12 className,
10− icon,
13+ icon = '→',
11 children,
14 children,
12 ...rest
15 ...rest
13}) {
16}) {
@@ -31,16 +34,19 @@ export default function Button({
31 className
34 className
32 )}
35 )}
33 style={{
36 style={{
37+ ...(disabled && {
34− ...(disabled && { pointerEvents: 'none', cursor: 'not-allowed' }),
38+ pointerEvents: 'none',
39+ cursor: 'not-allowed',
40+ }),
35 }}
41 }}
36 {...rest}>
42 {...rest}>
37− <Grid.Item className="flex-row justify-between" full>
43+ <Grid.Item as={Flex} justify="between" full>
38 <span className={cn(textColor)}>{children}</span>
44 <span className={cn(textColor)}>{children}</span>
39 <div className={cn('pl4', textColor)}>{icon}</div>
45 <div className={cn('pl4', textColor)}>{icon}</div>
40 </Grid.Item>
46 </Grid.Item>
41 {detail && (
47 {detail && (
42− <Grid.Item full>
48+ <Grid.Item as={HelpText} full>
43− <span className="gray4 f6">{detail}</span>
49+ {detail}
44 </Grid.Item>
50 </Grid.Item>
45 )}
51 )}
46 </Grid>
52 </Grid>
src/indigo-react/components/Typography.jsnew file+23
@@ -0,0 +1,23 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+import getComponent from '../lib/getComponent';
5+
6+const typeWith = (DefaultComponent, defaultClassName) =>
7+ function({ as = DefaultComponent, className, ...rest }) {
8+ const Component = getComponent(as);
9+ return <Component className={cn(defaultClassName, className)} {...rest} />;
10+ };
11+
12+export const H1 = typeWith('h1');
13+export const H2 = typeWith('h2');
14+export const H3 = typeWith('h3');
15+export const H4 = typeWith('h4');
16+export const H5 = typeWith('h5');
17+export const H6 = typeWith('h6');
18+export const Caption = typeWith('caption');
19+export const P = typeWith('p');
20+
21+export const Breadcrumb = typeWith('span', 'f6');
22+export const Text = typeWith('span', 'f5');
23+export const HelpText = typeWith('span', 'f6 gray4');
src/indigo-react/index.js+2
@@ -3,3 +3,5 @@ export { default as Grid } from './components/Grid';
3export { default as Flex } from './components/Flex';
3export { default as Flex } from './components/Flex';
4export { default as Input } from './components/Input';
4export { default as Input } from './components/Input';
5export { default as Button } from './components/Button';
5export { default as Button } from './components/Button';
6+
7+export * from './components/Typography';
src/lib/routeNames.js+1−1
@@ -3,7 +3,7 @@ export const ROUTE_NAMES = {
3 //
3 //
4 INVITE_TICKET: Symbol('INVITE_TICKET'),
4 INVITE_TICKET: Symbol('INVITE_TICKET'),
5 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
5 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
6− INVITES_SEND: Symbol('INVITES_SEND'),
6+ INVITE: Symbol('INVITE'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
8 //
8 //
9 ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
9 ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
src/lib/router.js+4−7
@@ -4,7 +4,6 @@ import * as need from './need';
4
4
5import Landing from '../views/Landing';
5import Landing from '../views/Landing';
6import InviteTicket from '../views/InviteTicket';
6import InviteTicket from '../views/InviteTicket';
7−import InvitesSend from '../views/InvitesSend.js';
8import InvitesManage from '../views/InvitesManage.js';
7import InvitesManage from '../views/InvitesManage.js';
9import AcceptTransfer from '../views/AcceptTransfer';
8import AcceptTransfer from '../views/AcceptTransfer';
10import CancelTransfer from '../views/CancelTransfer';
9import CancelTransfer from '../views/CancelTransfer';
@@ -31,16 +30,17 @@ import Shards from '../views/Shards';
31import Ticket from '../views/Ticket';
30import Ticket from '../views/Ticket';
32import Transfer from '../views/Transfer';
31import Transfer from '../views/Transfer';
33import Wallet from '../views/Wallet';
32import Wallet from '../views/Wallet';
33+import Invite from 'views/Invite';
34
34
35import { EthereumWallet } from './wallet';
35import { EthereumWallet } from './wallet';
36import { renderNetworkType } from './network';
36import { renderNetworkType } from './network';
37import { ROUTE_NAMES } from './routeNames';
37import { ROUTE_NAMES } from './routeNames';
38
38
39−const ROUTES = {
39+export const ROUTES = {
40 [ROUTE_NAMES.DEFAULT]: Landing,
40 [ROUTE_NAMES.DEFAULT]: Landing,
41 [ROUTE_NAMES.LANDING]: Landing,
41 [ROUTE_NAMES.LANDING]: Landing,
42 [ROUTE_NAMES.INVITE_TICKET]: InviteTicket,
42 [ROUTE_NAMES.INVITE_TICKET]: InviteTicket,
43− [ROUTE_NAMES.INVITES_SEND]: InvitesSend,
43+ [ROUTE_NAMES.INVITE]: Invite,
44 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
44 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
45 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
45 [ROUTE_NAMES.ACCEPT_TRANSFER]: AcceptTransfer,
46 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
46 [ROUTE_NAMES.CANCEL_TRANSFER]: CancelTransfer,
@@ -77,7 +77,7 @@ export const getRouteBreadcrumb = ({
77 return 'Invite code';
77 return 'Invite code';
78 case ROUTE_NAMES.INVITE_TRANSACTIONS:
78 case ROUTE_NAMES.INVITE_TRANSACTIONS:
79 return 'Setting up new wallet';
79 return 'Setting up new wallet';
80− case ROUTE_NAMES.INVITES_SEND:
80+ case ROUTE_NAMES.INVITE:
81 return 'Send invites';
81 return 'Send invites';
82 case ROUTE_NAMES.INVITES_MANAGE:
82 case ROUTE_NAMES.INVITES_MANAGE:
83 return 'Manage invites';
83 return 'Manage invites';
@@ -138,6 +138,3 @@ export const getRouteBreadcrumb = ({
138 return 'Bridge';
138 return 'Bridge';
139 }
139 }
140};
140};
141−
142−const router = route => ROUTES[route.name];
143−export default router;
src/lib/useCurrentPointName.jsnew file+12
@@ -0,0 +1,12 @@
1+import * as ob from 'urbit-ob';
2+
3+import { usePointCursor } from 'store/pointCursor';
4+
5+export default function useCurrentPointName() {
6+ const { pointCursor } = usePointCursor();
7+
8+ return pointCursor.matchWith({
9+ Nothing: () => null,
10+ Just: p => ob.patp(p.value),
11+ });
12+}
src/lib/useInvites.js+10−4
@@ -9,7 +9,7 @@ import useLifecycle from './useLifecycle';
9export default function useInvites(point) {
9export default function useInvites(point) {
10 const { contracts } = useNetwork();
10 const { contracts } = useNetwork();
11
11
12− const [invites, _setInvites] = useState(Maybe.Nothing());
12+ const [availableInvites, _setAvailableInvites] = useState(Maybe.Nothing());
13
13
14 const fetchInvites = useCallback(() => {
14 const fetchInvites = useCallback(() => {
15 const _contracts = contracts.getOrElse(null);
15 const _contracts = contracts.getOrElse(null);
@@ -23,12 +23,18 @@ export default function useInvites(point) {
23 _contracts,
23 _contracts,
24 point
24 point
25 );
25 );
26− _setInvites(Maybe.Just(count));
26+ _setAvailableInvites(Maybe.Just(count));
27 })();
27 })();
28− }, [_setInvites, point, contracts]);
28+ }, [_setAvailableInvites, point, contracts]);
29
29
30 // fetch invites on first render
30 // fetch invites on first render
31 useLifecycle(fetchInvites);
31 useLifecycle(fetchInvites);
32
32
33+ return {
34+ availableInvites,
33− return { invites, fetchInvites };
35+ fetchInvites,
36+ // TODO: look up sent/accepted
37+ sentInvites: Maybe.Just(6),
38+ acceptedInvites: Maybe.Just(5),
39+ };
34}
40}
src/lib/useRouter.jsnew file+102
@@ -0,0 +1,102 @@
1+import { useCallback, useMemo, useState, useEffect } from 'react';
2+import { last, includes as _includes } from 'lodash';
3+
4+/**
5+ * @param primary whether or not this is the top-level router
6+ * @param names map of string to view key, used for consumer references
7+ * @param views map of view keys to react component views
8+ * @param initialRoutes initial set of routes following the { key, data } format
9+ */
10+export default function useRouter({
11+ primary = false,
12+ names = {},
13+ views = {},
14+ initialRoutes = [],
15+}) {
16+ const [routes, setRoutes] = useState(initialRoutes);
17+
18+ const push = useCallback(
19+ (key, data) => {
20+ if (!views[key]) {
21+ throw new Error(
22+ `Invalid Route: ${key}. Possible Routes: ${Object.keys(views).join(
23+ ', '
24+ )}`
25+ );
26+ }
27+
28+ setRoutes(routes => [...routes, { key, data }]);
29+ },
30+ [setRoutes, views]
31+ );
32+ const popAndPush = useCallback(
33+ (key, data = {}) => {
34+ if (!views[key]) {
35+ throw new Error(
36+ `Invalid Route: ${key}. Possible Routes: ${Object.keys(views).join(
37+ ', '
38+ )}`
39+ );
40+ }
41+
42+ setRoutes(routes => [
43+ ...routes.slice(0, routes.length - 1),
44+ { key, data },
45+ ]);
46+ },
47+ [setRoutes, views]
48+ );
49+ const pop = useCallback(
50+ (count = 1) =>
51+ setRoutes(routes =>
52+ routes.length > 1 ? [...routes.slice(0, routes.length - count)] : routes
53+ ),
54+ // ^ can't pop root route
55+ [setRoutes]
56+ );
57+ const peek = useCallback(() => last(routes), [routes]);
58+ const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
59+ routes,
60+ ]);
61+ const size = routes.length;
62+ const data = useMemo(() => last(routes).data, [routes]);
63+ const Route = useMemo(() => views[peek().key], [views, peek]);
64+
65+ // Scroll to top of page with each route transition on primary navigation
66+ useEffect(() => {
67+ if (!primary) {
68+ return;
69+ }
70+
71+ window.scrollTo(0, 0);
72+ }, [routes, primary]);
73+
74+ // capture browser pop in primary router
75+ useEffect(() => {
76+ if (!primary) {
77+ return;
78+ }
79+
80+ window.history.pushState(null, null, null);
81+
82+ window.onpopstate = e => {
83+ window.history.pushState(null, null, null);
84+ pop();
85+ };
86+
87+ // TODO: disposer functon
88+ }, [pop, primary]);
89+
90+ return {
91+ Route,
92+ names,
93+ routes,
94+ push,
95+ popAndPush,
96+ pop,
97+ peek,
98+ size,
99+ includes,
100+ data,
101+ };
102+}
src/store/history.js+12−59
@@ -1,67 +1,20 @@
1−import React, {
1+import React, { createContext, forwardRef, useContext } from 'react';
2− createContext,
3− forwardRef,
4− useContext,
5− useState,
6− useEffect,
7−} from 'react';
8−import { last, includes } from 'lodash';
9
2
10−import { BRIDGE_ERROR } from '../lib/error';
3+import useRouter from 'lib/useRouter';
11−import { ROUTE_NAMES } from '../lib/routeNames';
12
4
13−export const HistoryContext = createContext(null);
5+/**
14−
6+ * `useHistory` is just a global `useRouter`
15−function _useHistory(initialRoutes = []) {
7+ * and we call it `history` to distinguish it within a local router.
16− const [history, setHistory] = useState(initialRoutes);
8+ * (see Invite.js for an example)
17−
9+ */
18− const value = {
19− routes: history,
20− push: (name, data) => {
21− if (!includes(ROUTE_NAMES, name)) {
22− throw BRIDGE_ERROR.INVALID_ROUTE;
23− }
24−
25− setHistory(history => [...history, { name, data }]);
26− },
27− popAndPush: (name, data = {}) =>
28− setHistory(history => [
29− ...history.slice(0, history.length - 1),
30− { name, data },
31− ]),
32− pop: (count = 1) =>
33− setHistory(history => [...history.slice(0, history.length - count)]),
34− peek: () => last(history),
35− size: history.length,
36− includes: name => includes(history.map(r => r.name), name),
37− data: last(history).data,
38− };
39−
40− // Scroll to top of page with each route transition.
41− useEffect(() => window.scrollTo(0, 0), [history]);
42
10
43− // set up handlers
11+export const HistoryContext = createContext(null);
44− useEffect(() => {
45− window.history.pushState(null, null, null);
46−
47− window.onpopstate = e => {
48− window.history.pushState(null, null, null);
49− value.pop();
50− };
51−
52− // TODO: disposer functon
53− });
54−
55− return value;
56−}
57
12
58−export function HistoryProvider({ initialRoutes, children }) {
13+export function HistoryProvider({ names, views, initialRoutes, children }) {
59− const history = _useHistory(initialRoutes);
14+ const router = useRouter({ names, views, initialRoutes, primary: true });
60
15
61 return (
16 return (
62− <HistoryContext.Provider value={history}>
17+ <HistoryContext.Provider value={router}>{children}</HistoryContext.Provider>
63− {children}
64− </HistoryContext.Provider>
65 );
18 );
66}
19}
67
20
@@ -74,6 +27,6 @@ export function useHistory() {
74export const withHistory = Component =>
27export const withHistory = Component =>
75 forwardRef((props, ref) => (
28 forwardRef((props, ref) => (
76 <HistoryContext.Consumer>
29 <HistoryContext.Consumer>
77− {history => <Component ref={ref} history={history} {...props} />}
30+ {router => <Component ref={ref} history={router} {...props} />}
78 </HistoryContext.Consumer>
31 </HistoryContext.Consumer>
79 ));
32 ));
src/views/Invite.jsnew file+123
@@ -0,0 +1,123 @@
1+import React, { useCallback } from 'react';
2+import * as need from 'lib/need';
3+import { Grid, H5, Text, Button } from 'indigo-react';
4+
5+import { usePointCursor } from 'store/pointCursor';
6+// import { usePointCache } from 'store/pointCache';
7+
8+import View from 'components/View';
9+import Crumbs from 'components/Crumbs';
10+
11+import useInvites from 'lib/useInvites';
12+import useSyncPoint from 'lib/useSyncPoint';
13+import useCurrentPointName from 'lib/useCurrentPointName';
14+
15+import { useHistory } from 'store/history';
16+import { ForwardButton } from 'components/Buttons';
17+import FooterButton from 'components/FooterButton';
18+import useRouter from 'lib/useRouter';
19+import InvitesSend from './InvitesSend';
20+
21+const kInviteNames = {
22+ URL: 'URL',
23+ EMAIL: 'EMAIL',
24+};
25+
26+const kInviteViews = {
27+ URL: InviteUrl,
28+ EMAIL: InvitesSend,
29+};
30+
31+const valOrLoading = val =>
32+ val.matchWith({
33+ Nothing: () => '▓',
34+ Just: p => p.value,
35+ });
36+
37+function Highlighted(props) {
38+ return <span className="red3" {...props} />;
39+}
40+
41+function InviteUrl({ names, push }) {
42+ const pushEmail = useCallback(() => push(names.EMAIL), [push, names]);
43+
44+ return (
45+ <>
46+ <Grid className="mt3">
47+ <Grid.Divider />
48+ <Grid.Item
49+ as={Button}
50+ detail="Generate a URL to send to one guest"
51+ full
52+ disabled>
53+ Generate Invite
54+ </Grid.Item>
55+ <Grid.Divider />
56+ <Grid.Item
57+ as={ForwardButton}
58+ onClick={pushEmail}
59+ detail="Send invitation to your guest's email"
60+ full>
61+ Send Invite via Email
62+ </Grid.Item>
63+ </Grid>
64+ <FooterButton detail="i.e. Ethereum Address, Text File" disabled>
65+ Advanced
66+ </FooterButton>
67+ </>
68+ );
69+}
70+
71+export default function Invite() {
72+ const history = useHistory();
73+ const { Route, ...routeProps } = useRouter({
74+ names: kInviteNames,
75+ views: kInviteViews,
76+ initialRoutes: [{ key: kInviteNames.URL }],
77+ });
78+ const { pointCursor } = usePointCursor();
79+ const point = need.pointCursor(pointCursor);
80+ const name = useCurrentPointName();
81+
82+ // sync the current cursor
83+ useSyncPoint(point);
84+
85+ // fetch the invites for the current cursor
86+ const { availableInvites, sentInvites, acceptedInvites } = useInvites(point);
87+ const availableInvitesText = valOrLoading(availableInvites);
88+ const sentInvitesText = valOrLoading(sentInvites);
89+ const acceptedInvitesText = valOrLoading(acceptedInvites);
90+
91+ return (
92+ <View>
93+ <Grid className="mb3">
94+ <Grid.Item
95+ as={Crumbs}
96+ routes={[
97+ {
98+ text: name,
99+ action: () => history.pop(),
100+ },
101+ {
102+ text: 'Invite',
103+ },
104+ ]}
105+ full
106+ />
107+ <Grid.Item as={H5} className="mv4" full>
108+ Invite
109+ </Grid.Item>
110+ <Grid.Item as={Text} full>
111+ You currently have{' '}
112+ <Highlighted>{availableInvitesText} invitations</Highlighted> left.
113+ <br />
114+ Out of the <Highlighted>{sentInvitesText}</Highlighted> invites you
115+ sent, <Highlighted>{acceptedInvitesText}</Highlighted> have been
116+ accepted.
117+ </Grid.Item>
118+ </Grid>
119+ {/* TODO(shrugs): use context isntead of this routeProps nonsense */}
120+ <Route {...routeProps} />
121+ </View>
122+ );
123+}
src/views/Landing.js+13−12
@@ -1,5 +1,5 @@
1import React from 'react';
1import React from 'react';
2−import { H1, P } from '../components/old/Base';
2+import { H1, P, HelpText } from 'indigo-react';
3
3
4import { ROUTE_NAMES } from '../lib/routeNames';
4import { ROUTE_NAMES } from '../lib/routeNames';
5import { useHistory } from '../store/history';
5import { useHistory } from '../store/history';
@@ -19,25 +19,26 @@ function Landing(props) {
19 address space.
19 address space.
20 </P>
20 </P>
21
21
22+ <ForwardButton
23+ className="mt3"
24+ detail="If you were sent an Azimuth invite code."
22− <ForwardButton onClick={() => history.push(ROUTE_NAMES.INVITE_TICKET)}>
25+ onClick={() => history.push(ROUTE_NAMES.INVITE_TICKET)}>
23 Claim an invite
26 Claim an invite
24 </ForwardButton>
27 </ForwardButton>
25
28
26− <P>If you were sent an Azimuth invite code, start here.</P>
29+ <ForwardButton
27−
30+ className="mt3"
31+ detail="If you own Azimuth assets and want to manage them in some way."
28− <ForwardButton onClick={() => history.push(ROUTE_NAMES.NETWORK)}>
32+ onClick={() => history.push(ROUTE_NAMES.NETWORK)}>
29 Unlock a Wallet
33 Unlock a Wallet
30 </ForwardButton>
34 </ForwardButton>
31
35
32− <P>
36+ <ForwardButton
33− If you own Azimuth assets and want to manage them in some way, start
37+ className="mt3"
34− here. You'll need either your Urbit ticket or a keypair.
38+ detail="View an Azimuth point without signing into a wallet."
35− </P>
36−
37− <ForwardButton onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
39+ onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
38 View a point
40 View a point
39 </ForwardButton>
41 </ForwardButton>
40− <P>{'View an Azimuth point without signing into a wallet.'}</P>
41 </View>
42 </View>
42 );
43 );
43}
44}
src/views/Mnemonic.js+3−1
@@ -65,8 +65,10 @@ export default function Mnemonic() {
65 />
65 />
66
66
67 <ForwardButton
67 <ForwardButton
68+ className="mt3"
68 disabled={Nothing.hasInstance(wallet)}
69 disabled={Nothing.hasInstance(wallet)}
69− onClick={() => history.popAndPush(ROUTE_NAMES.POINTS)}>
70+ onClick={() => history.popAndPush(ROUTE_NAMES.POINTS)}
71+ solid>
70 Continue
72 Continue
71 </ForwardButton>
73 </ForwardButton>
72 </View>
74 </View>
src/views/Point.js+12−20
@@ -8,13 +8,13 @@ import { usePointCursor } from 'store/pointCursor';
8import View from 'components/View';
8import View from 'components/View';
9import Passport from 'components/Passport';
9import Passport from 'components/Passport';
10import { ForwardButton } from 'components/Buttons';
10import { ForwardButton } from 'components/Buttons';
11−import Footer from 'components/Footer';
12
11
13import useInvites from 'lib/useInvites';
12import useInvites from 'lib/useInvites';
14import useSyncPoint from 'lib/useSyncPoint';
13import useSyncPoint from 'lib/useSyncPoint';
15import { ROUTE_NAMES } from 'lib/routeNames';
14import { ROUTE_NAMES } from 'lib/routeNames';
16
15
17import { useHistory } from 'store/history';
16import { useHistory } from 'store/history';
17+import FooterButton from 'components/FooterButton';
18
18
19export default function Point() {
19export default function Point() {
20 const history = useHistory();
20 const history = useHistory();
@@ -24,11 +24,9 @@ export default function Point() {
24 const point = need.pointCursor(pointCursor);
24 const point = need.pointCursor(pointCursor);
25
25
26 // fetch the invites for the current cursor
26 // fetch the invites for the current cursor
27− const { invites } = useInvites(point);
27+ const { availableInvites } = useInvites(point);
28
28
29− const goAdmin = useCallback(() => {}, []);
30− // ^ TODO: push .ADMIN
31− const goInvite = useCallback(() => history.push(ROUTE_NAMES.INVITES_MANAGE), [
29+ const goInvite = useCallback(() => history.push(ROUTE_NAMES.INVITE), [
32 history,
30 history,
33 ]);
31 ]);
34
32
@@ -42,7 +40,7 @@ export default function Point() {
42 <Passport point={point} />
40 <Passport point={point} />
43 <Grid className="pt2">
41 <Grid className="pt2">
44 <Grid.Item full>
42 <Grid.Item full>
45− <ForwardButton onClick={goAdmin}>Admin</ForwardButton>
43+ <ForwardButton disabled>Admin</ForwardButton>
46 </Grid.Item>
44 </Grid.Item>
47 <Grid.Divider />
45 <Grid.Divider />
48 <Grid.Item full>
46 <Grid.Item full>
@@ -51,20 +49,14 @@ export default function Point() {
51 </ForwardButton>
49 </ForwardButton>
52 </Grid.Item>
50 </Grid.Item>
53 </Grid>
51 </Grid>
54− <Footer>
52+
55− <Grid className="pt2">
56− <Grid.Divider />
57− <Grid.Item full>
58− <ForwardButton onClick={goInvite}>
53+ <FooterButton onClick={goInvite}>
59− Invite
54+ Invite
60− {invites.matchWith({
55+ {availableInvites.matchWith({
61− Nothing: () => null,
56+ Nothing: () => null,
62− Just: p => <sup className="ml1">{p.value}</sup>,
57+ Just: p => <sup className="ml1">{p.value}</sup>,
63− })}
58+ })}
64− </ForwardButton>
59+ </FooterButton>
65− </Grid.Item>
66− </Grid>
67− </Footer>
68 </View>
60 </View>
69 );
61 );
70}
62}
src/views/Point/Actions.js+1−1
@@ -111,7 +111,7 @@ function Actions(props) {
111 disabled={!isActiveOwner || !online || !hasInvites}
111 disabled={!isActiveOwner || !online || !hasInvites}
112 prop-size={'sm'}
112 prop-size={'sm'}
113 prop-type={'link'}
113 prop-type={'link'}
114− onClick={() => history.push(ROUTE_NAMES.INVITES_SEND)}>
114+ onClick={() => history.push(ROUTE_NAMES.INVITE)}>
115 {'Send invites ('}
115 {'Send invites ('}
116 {invites.matchWith({
116 {invites.matchWith({
117 Nothing: () => '?',
117 Nothing: () => '?',