on master help sign in
refactor: unified back button, fix history back button reliability
f9d545dMatt Condon (shrugs)
src/components/MiniBackButton.js+1−1
@@ -5,7 +5,7 @@ import { IconButton } from 'indigo-react';
5import { ReactComponent as Back } from 'assets/back.svg';
5import { ReactComponent as Back } from 'assets/back.svg';
6
6
7export default ({ className, ...rest }) => (
7export default ({ className, ...rest }) => (
8− <IconButton {...rest} flush>
8+ <IconButton {...rest}>
9 <Back className={cn('black', className)} />
9 <Back className={cn('black', className)} />
10 </IconButton>
10 </IconButton>
11);
11);
src/components/View.js+41−9
@@ -1,18 +1,38 @@
1−import React from 'react';
1+import React, { useCallback } from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3import { Flex } from 'indigo-react';
3import { Flex } from 'indigo-react';
4
4
5import Footer from './Footer';
5import Footer from './Footer';
6import useBreakpoints from 'lib/useBreakpoints';
6import useBreakpoints from 'lib/useBreakpoints';
7+import MiniBackButton from './MiniBackButton';
8+import { useHistory } from 'store/history';
7
9
8// View is a top-level component that all Views must render to inherit styling
10// View is a top-level component that all Views must render to inherit styling
9−function View({ className, children, inset = false, full = false, ...rest }) {
11+function View({
12+ className,
13+ children,
14+ inset = false,
15+ full = false,
16+ pop,
17+ ...rest
18+}) {
19+ const { size } = useHistory();
10 const isMobile = useBreakpoints([true, false, false]);
20 const isMobile = useBreakpoints([true, false, false]);
11 const shouldInset = inset && !isMobile;
21 const shouldInset = inset && !isMobile;
12
22
23+ const insetPadding = cn({
24+ pt10: shouldInset,
25+ pt5: !shouldInset,
26+ });
27+
28+ const goBack = useCallback(() => pop(), [pop]);
29+
30+ const showBackButton = size > 1 && !!pop;
31+
13 return (
32 return (
14 <Flex
33 <Flex
34+ row={!isMobile}
15− col
35+ col={isMobile}
16 justify="between"
36 justify="between"
17 className={cn(
37 className={cn(
18 'minh-100 ph5',
38 'minh-100 ph5',
@@ -24,13 +44,25 @@ function View({ className, children, inset = false, full = false, ...rest }) {
24 )}
44 )}
25 {...rest}>
45 {...rest}>
26 <Flex.Item
46 <Flex.Item
47+ as={Flex}
48+ col
49+ style={{ width: '48px' }}
27− className={cn('pb5', {
50+ className={cn(insetPadding)}>
28− pt10: shouldInset,
51+ {showBackButton && (
29− pt5: !shouldInset,
52+ <MiniBackButton
30− })}>
53+ hpadding={!isMobile}
31− {children}
54+ vpadding={isMobile}
55+ onClick={goBack}
56+ />
57+ )}
32 </Flex.Item>
58 </Flex.Item>
59+
60+ <Flex.Item flex={1} as={Flex} col justify="between">
61+ <Flex.Item className={cn('pb5', insetPadding)}>{children}</Flex.Item>
33− <Footer.Target />
62+ <Flex.Item as={Footer.Target} />
63+ </Flex.Item>
64+
65+ <Flex.Item style={{ width: '48px' }} className={cn(insetPadding)} />
34 </Flex>
66 </Flex>
35 );
67 );
36}
68}
src/indigo-react/components/IconButton.js+5−3
@@ -10,7 +10,8 @@ export default function IconButton({
10 disabled = false,
10 disabled = false,
11 secondary = false,
11 secondary = false,
12 solid = false,
12 solid = false,
13− flush = false, // if true, remove horizontal padding
13+ hpadding = true,
14+ vpadding = true,
14 ...rest
15 ...rest
15}) {
16}) {
16 return (
17 return (
@@ -19,9 +20,10 @@ export default function IconButton({
19 align="center"
20 align="center"
20 justify="center"
21 justify="center"
21 className={cn(
22 className={cn(
22− 'pointer pv2',
23+ 'pointer',
23 {
24 {
24− ph2: !flush,
25+ ph2: hpadding,
26+ pv2: vpadding,
25 },
27 },
26 {
28 {
27 'bg-black': solid && !secondary && !disabled,
29 'bg-black': solid && !secondary && !disabled,
src/lib/routeNames.js−1
@@ -15,7 +15,6 @@ export const ROUTE_NAMES = {
15 //
15 //
16 ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
16 ACCEPT_TRANSFER: Symbol('ACCEPT_TRANSFER'),
17 CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER'),
17 CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER'),
18− LANDING: Symbol('LANDING'),
19 VIEW_POINT: Symbol('VIEW_POINT'),
18 VIEW_POINT: Symbol('VIEW_POINT'),
20 POINTS: Symbol('POINTS'),
19 POINTS: Symbol('POINTS'),
21 POINT: Symbol('POINT'),
20 POINT: Symbol('POINT'),
src/lib/useRouter.js+20−22
@@ -4,13 +4,11 @@ import { last, includes as _includes, findIndex } from 'lodash';
4const NULL_DATA = {};
4const NULL_DATA = {};
5
5
6/**
6/**
7− * @param primary whether or not this is the top-level router
8 * @param names map of string to view key, used for consumer references
7 * @param names map of string to view key, used for consumer references
9 * @param views map of view keys to react component views
8 * @param views map of view keys to react component views
10 * @param initialRoutes initial set of routes following the { key, data } format
9 * @param initialRoutes initial set of routes following the { key, data } format
11 */
10 */
12export default function useRouter({
11export default function useRouter({
13− primary = false,
14 names = {},
12 names = {},
15 views = {},
13 views = {},
16 initialRoutes = [],
14 initialRoutes = [],
@@ -52,15 +50,27 @@ export default function useRouter({
52 );
50 );
53 const pop = useCallback(
51 const pop = useCallback(
54 (count = 1) => {
52 (count = 1) => {
55− if (size > 1) {
53+ if (size <= 1) {
56− // pop as expected
54+ // if we are at the root, pass this event to our parent
57− return setRoutes(routes => [...routes.slice(0, routes.length - count)]);
55+ if (oldPopState.current) {
56+ console.log('attempting to pass to parent');
57+ window.history.back();
58− }
58+ }
59+
60+ console.log('at root');
59
61
60− // if we are at the root, pass this event to our parent
62+ return;
61− if (oldPopState.current) {
62− window.history.back();
63 }
63 }
64+
65+ // on pop, tell the browser of a new state to avoid giving the user
66+ // the ability to go forward
67+ // TODO: allow the user to go forward by storing our data in history
68+ // and using the url for other state
69+ // (or just importing an actually good router lib)
70+ window.history.pushState(null, null, null);
71+
72+ // pop as expected
73+ return setRoutes(routes => [...routes.slice(0, routes.length - count)]);
64 },
74 },
65 [size, setRoutes, oldPopState]
75 [size, setRoutes, oldPopState]
66 );
76 );
@@ -102,19 +112,7 @@ export default function useRouter({
102
112
103 // construct new onpopstate handler
113 // construct new onpopstate handler
104 window.onpopstate = e => {
114 window.onpopstate = e => {
105− if (size <= 1 && oldPopState.current) {
115+ e.stopImmediatePropagation();
106− // if this is the root route and there's a parent handler,
107− // give the event to the handler
108− return oldPopState.current(e);
109− }
110−
111− // on pop, tell the browser of a new state to avoid giving the user
112− // the ability to go forward
113− // TODO: allow the user to go forward by storing our data in history
114− // and using the url for other state
115− window.history.pushState(null, null, null);
116−
117− // then update our local state for rendering
118 pop();
116 pop();
119 };
117 };
120
118
src/store/history.js+5−2
@@ -1,6 +1,7 @@
1import React, { createContext, forwardRef, useContext } from 'react';
1import React, { createContext, forwardRef, useContext } from 'react';
2
2
3import useRouter from 'lib/useRouter';
3import useRouter from 'lib/useRouter';
4+import { LocalRouterProvider } from 'lib/LocalRouter';
4
5
5/**
6/**
6 * `useHistory` is just a global `useRouter`
7 * `useHistory` is just a global `useRouter`
@@ -11,10 +12,12 @@ import useRouter from 'lib/useRouter';
11export const HistoryContext = createContext(null);
12export const HistoryContext = createContext(null);
12
13
13export function HistoryProvider({ names, views, initialRoutes, children }) {
14export function HistoryProvider({ names, views, initialRoutes, children }) {
14− const router = useRouter({ names, views, initialRoutes, primary: true });
15+ const router = useRouter({ names, views, initialRoutes });
15
16
16 return (
17 return (
17− <HistoryContext.Provider value={router}>{children}</HistoryContext.Provider>
18+ <HistoryContext.Provider value={router}>
19+ <LocalRouterProvider value={router}>{children}</LocalRouterProvider>
20+ </HistoryContext.Provider>
18 );
21 );
19}
22}
20
23
src/style/indigo-proposals.scss+2−1
@@ -1,6 +1,7 @@
1.mw1 {
1.mw1 {
2 margin: 0 auto;
2 margin: 0 auto;
3− max-width: 30rem;
3+ max-width: calc(32rem + 48px * 2);
4+ // TODO: this css is gross, fix
4}
5}
5
6
6.mw2 {
7.mw2 {
src/views/AcceptTransfer.js+3−6
@@ -6,7 +6,6 @@ import * as azimuth from 'azimuth-js';
6import { useNetwork } from 'store/network';
6import { useNetwork } from 'store/network';
7import { usePointCursor } from 'store/pointCursor';
7import { usePointCursor } from 'store/pointCursor';
8import { usePointCache } from 'store/pointCache';
8import { usePointCache } from 'store/pointCache';
9−import { useHistory } from 'store/history';
10import { useWallet } from 'store/wallet';
9import { useWallet } from 'store/wallet';
11
10
12import * as need from 'lib/need';
11import * as need from 'lib/need';
@@ -14,9 +13,9 @@ import useCurrentPointName from 'lib/useCurrentPointName';
14import useEthereumTransaction from 'lib/useEthereumTransaction';
13import useEthereumTransaction from 'lib/useEthereumTransaction';
15import { GAS_LIMITS } from 'lib/constants';
14import { GAS_LIMITS } from 'lib/constants';
16import useLifecycle from 'lib/useLifecycle';
15import useLifecycle from 'lib/useLifecycle';
16+import { useLocalRouter } from 'lib/LocalRouter';
17
17
18import ViewHeader from 'components/ViewHeader';
18import ViewHeader from 'components/ViewHeader';
19−import MiniBackButton from 'components/MiniBackButton';
20import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
19import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
21import View from 'components/View';
20import View from 'components/View';
22
21
@@ -58,17 +57,15 @@ function useAcceptTransfer() {
58}
57}
59
58
60export default function AcceptTransfer() {
59export default function AcceptTransfer() {
61− const { pop } = useHistory();
60+ const { pop } = useLocalRouter();
62
61
63 const name = useCurrentPointName();
62 const name = useCurrentPointName();
64
63
65 const { completed, bind } = useAcceptTransfer();
64 const { completed, bind } = useAcceptTransfer();
66
65
67 return (
66 return (
68− <View inset>
67+ <View pop={pop} inset>
69 <Grid>
68 <Grid>
70− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
71−
72 <Grid.Item full as={ViewHeader}>
69 <Grid.Item full as={ViewHeader}>
73 Accept Transfer
70 Accept Transfer
74 </Grid.Item>
71 </Grid.Item>
src/views/Activate/ActivateCode.js+1−1
@@ -122,7 +122,7 @@ export default function ActivateCode() {
122 useSyncKnownPoints([derivedPoint.getOrElse(null)].filter(p => p !== null));
122 useSyncKnownPoints([derivedPoint.getOrElse(null)].filter(p => p !== null));
123
123
124 return (
124 return (
125− <View>
125+ <View inset>
126 <Grid>
126 <Grid>
127 <Grid.Item as={Passport} point={derivedPoint} full />
127 <Grid.Item as={Passport} point={derivedPoint} full />
128 <Grid.Item as={H4} className="mt3 mb2" full>
128 <Grid.Item as={H4} className="mt3 mb2" full>
src/views/Activate/PassportTransfer.js+1−4
@@ -59,7 +59,6 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
59 const goToLogin = useCallback(
59 const goToLogin = useCallback(
60 () =>
60 () =>
61 replaceWith([
61 replaceWith([
62− { key: names.LANDING },
63 { key: names.LOGIN },
62 { key: names.LOGIN },
64 { key: names.POINTS },
63 { key: names.POINTS },
65 { key: names.POINT },
64 { key: names.POINT },
@@ -75,13 +74,11 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
75 // we only need to clear activate because that will unmount the passport
74 // we only need to clear activate because that will unmount the passport
76 // router (which will be set to initialRoutes when mounted again)
75 // router (which will be set to initialRoutes when mounted again)
77
76
78− // 1) replace history
79− replaceWith([{ key: names.LANDING }, { key: names.ACTIVATE }]);
80 // 2) reset local router
77 // 2) reset local router
81 resetActivateRouter();
78 resetActivateRouter();
82 // 3) clear the state
79 // 3) clear the state
83 resetActivateFlow();
80 resetActivateFlow();
84− }, [replaceWith, names, resetActivateRouter, resetActivateFlow]);
81+ }, [resetActivateRouter, resetActivateFlow]);
85
82
86 const handleUpdate = useCallback(
83 const handleUpdate = useCallback(
87 ({ type, state, value }) => {
84 ({ type, state, value }) => {
src/views/Admin.js+1−1
@@ -52,7 +52,7 @@ export default function Admin() {
52 });
52 });
53
53
54 return (
54 return (
55− <View inset>
55+ <View pop={router.pop} inset>
56 <LocalRouterProvider value={router}>
56 <LocalRouterProvider value={router}>
57 <Grid className="mb4">
57 <Grid className="mb4">
58 <Grid.Item
58 <Grid.Item
src/views/Admin/AdminCancelTransfer.js−3
@@ -16,7 +16,6 @@ import { ETH_ZERO_ADDR } from 'lib/wallet';
16import useLifecycle from 'lib/useLifecycle';
16import useLifecycle from 'lib/useLifecycle';
17
17
18import ViewHeader from 'components/ViewHeader';
18import ViewHeader from 'components/ViewHeader';
19−import MiniBackButton from 'components/MiniBackButton';
20import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
19import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
21
20
22function useCancelTransfer() {
21function useCancelTransfer() {
@@ -59,8 +58,6 @@ export default function AdminCancelTransfer() {
59
58
60 return (
59 return (
61 <Grid>
60 <Grid>
62− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
63−
64 <Grid.Item full as={ViewHeader}>
61 <Grid.Item full as={ViewHeader}>
65 Cancel Outgoing Transfer
62 Cancel Outgoing Transfer
66 </Grid.Item>
63 </Grid.Item>
src/views/Admin/AdminEditPermissions.js+1−3
@@ -18,10 +18,9 @@ import useCurrentPermissions from 'lib/useCurrentPermissions';
18import ViewHeader from 'components/ViewHeader';
18import ViewHeader from 'components/ViewHeader';
19import { ForwardButton } from 'components/Buttons';
19import { ForwardButton } from 'components/Buttons';
20import { matchBlinkyDate } from 'components/Blinky';
20import { matchBlinkyDate } from 'components/Blinky';
21−import MiniBackButton from 'components/MiniBackButton';
22
21
23export default function AdminEditPermissions() {
22export default function AdminEditPermissions() {
24− const { pop, push, names } = useLocalRouter();
23+ const { push, names } = useLocalRouter();
25 const { contracts } = useNetwork();
24 const { contracts } = useNetwork();
26 const { pointCursor } = usePointCursor();
25 const { pointCursor } = usePointCursor();
27 const { getDetails, getRekeyDate } = usePointCache();
26 const { getDetails, getRekeyDate } = usePointCache();
@@ -92,7 +91,6 @@ export default function AdminEditPermissions() {
92
91
93 return (
92 return (
94 <Grid>
93 <Grid>
95− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
96 <Grid.Item full as={ViewHeader}>
94 <Grid.Item full as={ViewHeader}>
97 Permissions
95 Permissions
98 </Grid.Item>
96 </Grid.Item>
src/views/Admin/AdminHome.js+1−3
@@ -11,10 +11,9 @@ import useKeyfileGenerator from 'lib/useKeyfileGenerator';
11import { ForwardButton } from 'components/Buttons';
11import { ForwardButton } from 'components/Buttons';
12import ViewHeader from 'components/ViewHeader';
12import ViewHeader from 'components/ViewHeader';
13import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
13import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
14−import MiniBackButton from 'components/MiniBackButton';
15
14
16export default function AdminHome() {
15export default function AdminHome() {
17− const { push, names, pop } = useLocalRouter();
16+ const { push, names } = useLocalRouter();
18 const { urbitWallet } = useWallet();
17 const { urbitWallet } = useWallet();
19
18
20 const { bind: keyfileBind } = useKeyfileGenerator();
19 const { bind: keyfileBind } = useKeyfileGenerator();
@@ -38,7 +37,6 @@ export default function AdminHome() {
38 return (
37 return (
39 <>
38 <>
40 <Grid>
39 <Grid>
41− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
42 <Grid.Item full as={ViewHeader}>
40 <Grid.Item full as={ViewHeader}>
43 Admin
41 Admin
44 </Grid.Item>
42 </Grid.Item>
src/views/Admin/AdminNetworkingKeys.js−3
@@ -34,7 +34,6 @@ import { useHexInput, useCheckboxInput } from 'lib/useInputs';
34import useKeyfileGenerator from 'lib/useKeyfileGenerator';
34import useKeyfileGenerator from 'lib/useKeyfileGenerator';
35
35
36import ViewHeader from 'components/ViewHeader';
36import ViewHeader from 'components/ViewHeader';
37−import MiniBackButton from 'components/MiniBackButton';
38import { BootArvoButton } from 'components/Buttons';
37import { BootArvoButton } from 'components/Buttons';
39import FooterButton from 'components/FooterButton';
38import FooterButton from 'components/FooterButton';
40import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
39import DownloadKeyfileButton from 'components/DownloadKeyfileButton';
@@ -315,8 +314,6 @@ export default function AdminNetworkingKeys() {
315 return (
314 return (
316 <>
315 <>
317 <Grid>
316 <Grid>
318− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
319−
320 <Grid.Item full as={ViewHeader}>
317 <Grid.Item full as={ViewHeader}>
321 {renderTitle()}
318 {renderTitle()}
322 </Grid.Item>
319 </Grid.Item>
src/views/Admin/AdminRedownload.js−2
@@ -13,7 +13,6 @@ import { useWallet } from 'store/wallet';
13import { Grid, P } from 'indigo-react';
13import { Grid, P } from 'indigo-react';
14import ViewHeader from 'components/ViewHeader';
14import ViewHeader from 'components/ViewHeader';
15import { useLocalRouter } from 'lib/LocalRouter';
15import { useLocalRouter } from 'lib/LocalRouter';
16−import MiniBackButton from 'components/MiniBackButton';
17
16
18export default function AdminRedownload() {
17export default function AdminRedownload() {
19 const { urbitWallet } = useWallet();
18 const { urbitWallet } = useWallet();
@@ -36,7 +35,6 @@ export default function AdminRedownload() {
36 return (
35 return (
37 <>
36 <>
38 <Grid>
37 <Grid>
39− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
40 <Grid.Item full as={ViewHeader}>
38 <Grid.Item full as={ViewHeader}>
41 Download Passport
39 Download Passport
42 </Grid.Item>
40 </Grid.Item>
src/views/Admin/AdminReticket.js+1−3
@@ -5,7 +5,6 @@ import { Grid, Flex } from 'indigo-react';
5import { LocalRouterProvider } from 'lib/LocalRouter';
5import { LocalRouterProvider } from 'lib/LocalRouter';
6import useRouter from 'lib/useRouter';
6import useRouter from 'lib/useRouter';
7
7
8−import MiniBackButton from 'components/MiniBackButton';
9import Steps from 'components/Steps';
8import Steps from 'components/Steps';
10
9
11import ReticketConfirm from './Reticket/ReticketConfirm';
10import ReticketConfirm from './Reticket/ReticketConfirm';
@@ -53,8 +52,7 @@ export default function AdminReticket() {
53 return (
52 return (
54 <LocalRouterProvider value={router}>
53 <LocalRouterProvider value={router}>
55 <Grid>
54 <Grid>
56− <Grid.Item full as={Flex} row justify="between" align="center">
55+ <Grid.Item full as={Flex} row align="center">
57− <Flex.Item as={MiniBackButton} onClick={() => router.pop()} />
58 <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
56 <Flex.Item as={Steps} num={router.size} total={STEPS.length} />
59 </Grid.Item>
57 </Grid.Item>
60 </Grid>
58 </Grid>
src/views/Admin/AdminSetProxy.js−3
@@ -20,7 +20,6 @@ import { useAddressInput, useCheckboxInput } from 'lib/useInputs';
20import useEthereumTransaction from 'lib/useEthereumTransaction';
20import useEthereumTransaction from 'lib/useEthereumTransaction';
21
21
22import ViewHeader from 'components/ViewHeader';
22import ViewHeader from 'components/ViewHeader';
23−import MiniBackButton from 'components/MiniBackButton';
24import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
23import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
25import { GAS_LIMITS } from 'lib/constants';
24import { GAS_LIMITS } from 'lib/constants';
26
25
@@ -153,8 +152,6 @@ export default function AdminSetProxy() {
153
152
154 return (
153 return (
155 <Grid>
154 <Grid>
156− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
157−
158 <Grid.Item full as={ViewHeader}>
155 <Grid.Item full as={ViewHeader}>
159 {properProxyType} Address
156 {properProxyType} Address
160 </Grid.Item>
157 </Grid.Item>
src/views/Admin/AdminTransfer.js−2
@@ -15,7 +15,6 @@ import useEthereumTransaction from 'lib/useEthereumTransaction';
15import { GAS_LIMITS } from 'lib/constants';
15import { GAS_LIMITS } from 'lib/constants';
16
16
17import ViewHeader from 'components/ViewHeader';
17import ViewHeader from 'components/ViewHeader';
18−import MiniBackButton from 'components/MiniBackButton';
19import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
20
19
21function useTransfer() {
20function useTransfer() {
@@ -64,7 +63,6 @@ export default function AdminTransfer() {
64
63
65 return (
64 return (
66 <Grid>
65 <Grid>
67− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
68 <Grid.Item full as={ViewHeader}>
66 <Grid.Item full as={ViewHeader}>
69 Transfer Point
67 Transfer Point
70 </Grid.Item>
68 </Grid.Item>
src/views/CancelTransfer.js+5−10
@@ -3,19 +3,14 @@ import React from 'react';
3import View from 'components/View';
3import View from 'components/View';
4
4
5import AdminCancelTransfer from './Admin/AdminCancelTransfer';
5import AdminCancelTransfer from './Admin/AdminCancelTransfer';
6−import { LocalRouterProvider } from 'lib/LocalRouter';
6+import { useLocalRouter } from 'lib/LocalRouter';
7−import { useHistory } from 'store/history';
8
7
9export default function CancelTransfer() {
8export default function CancelTransfer() {
10− const history = useHistory();
9+ const { pop } = useLocalRouter();
11
10
12 return (
11 return (
13− // TODO: this is a hack so AdminCancelTransfer works unmodified
14− // TODO: consolidate history & 'local' router providers
15− <LocalRouterProvider value={history}>
16− <View inset>
12+ <View pop={pop} inset>
17− <AdminCancelTransfer />
13+ <AdminCancelTransfer />
18− </View>
14+ </View>
19− </LocalRouterProvider>
20 );
15 );
21}
16}
src/views/CreateGalaxy.js+4−7
@@ -5,7 +5,6 @@ import { Grid, Text, Input } from 'indigo-react';
5import * as azimuth from 'azimuth-js';
5import * as azimuth from 'azimuth-js';
6
6
7import { useNetwork } from 'store/network';
7import { useNetwork } from 'store/network';
8−import { useHistory } from 'store/history';
9import { usePointCache } from 'store/pointCache';
8import { usePointCache } from 'store/pointCache';
10
9
11import * as need from 'lib/need';
10import * as need from 'lib/need';
@@ -13,12 +12,12 @@ import { useAddressInput, useGalaxyInput } from 'lib/useInputs';
13import useEthereumTransaction from 'lib/useEthereumTransaction';
12import useEthereumTransaction from 'lib/useEthereumTransaction';
14import { GAS_LIMITS } from 'lib/constants';
13import { GAS_LIMITS } from 'lib/constants';
15import patp2dec from 'lib/patp2dec';
14import patp2dec from 'lib/patp2dec';
15+import { isZeroAddress } from 'lib/wallet';
16+import { useLocalRouter } from 'lib/LocalRouter';
16
17
17import ViewHeader from 'components/ViewHeader';
18import ViewHeader from 'components/ViewHeader';
18−import MiniBackButton from 'components/MiniBackButton';
19import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
19import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
20import View from 'components/View';
20import View from 'components/View';
21−import { isZeroAddress } from 'lib/wallet';
22
21
23function useCreateGalaxy() {
22function useCreateGalaxy() {
24 const { contracts } = useNetwork();
23 const { contracts } = useNetwork();
@@ -42,7 +41,7 @@ function useCreateGalaxy() {
42}
41}
43
42
44export default function CreateGalaxy() {
43export default function CreateGalaxy() {
45− const { pop } = useHistory();
44+ const { pop } = useLocalRouter();
46 const { contracts } = useNetwork();
45 const { contracts } = useNetwork();
47 const _contracts = need.contracts(contracts);
46 const _contracts = need.contracts(contracts);
48
47
@@ -128,10 +127,8 @@ export default function CreateGalaxy() {
128 ]);
127 ]);
129
128
130 return (
129 return (
131− <View inset>
130+ <View pop={pop} inset>
132 <Grid>
131 <Grid>
133− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
134−
135 <Grid.Item full as={ViewHeader}>
132 <Grid.Item full as={ViewHeader}>
136 Create a Galaxy
133 Create a Galaxy
137 </Grid.Item>
134 </Grid.Item>
src/views/Invite.js+1−1
@@ -53,7 +53,7 @@ export default function Invite() {
53 const acceptedInvitesText = matchBlinky(acceptedInvites);
53 const acceptedInvitesText = matchBlinky(acceptedInvites);
54
54
55 return (
55 return (
56− <View inset>
56+ <View pop={router.pop} inset>
57 <LocalRouterProvider value={router}>
57 <LocalRouterProvider value={router}>
58 <Grid className="mb4">
58 <Grid className="mb4">
59 <Grid.Item
59 <Grid.Item
src/views/Invite/InviteEmail.js+10−18
@@ -29,7 +29,6 @@ import {
29 hexify,
29 hexify,
30} from 'lib/txn';
30} from 'lib/txn';
31import * as tank from 'lib/tank';
31import * as tank from 'lib/tank';
32−import { useLocalRouter } from 'lib/LocalRouter';
33import useArray from 'lib/useArray';
32import useArray from 'lib/useArray';
34import { buildEmailInputConfig } from 'lib/useInputs';
33import { buildEmailInputConfig } from 'lib/useInputs';
35import { MIN_PLANET, GAS_LIMITS, DEFAULT_GAS_PRICE_GWEI } from 'lib/constants';
34import { MIN_PLANET, GAS_LIMITS, DEFAULT_GAS_PRICE_GWEI } from 'lib/constants';
@@ -39,7 +38,6 @@ import useSetState from 'lib/useSetState';
39import pluralize from 'lib/pluralize';
38import pluralize from 'lib/pluralize';
40import useMailer from 'lib/useMailer';
39import useMailer from 'lib/useMailer';
41
40
42−import MiniBackButton from 'components/MiniBackButton';
43import LoadableButton from 'components/LoadableButton';
41import LoadableButton from 'components/LoadableButton';
44import Highlighted from 'components/Highlighted';
42import Highlighted from 'components/Highlighted';
45
43
@@ -96,7 +94,6 @@ const buildAccessoryFor = (dones, errors) => name => {
96// TODO: test with tank, successful txs
94// TODO: test with tank, successful txs
97export default function InviteEmail() {
95export default function InviteEmail() {
98 // TODO: resumption after error?
96 // TODO: resumption after error?
99− const { pop } = useLocalRouter();
100 const { contracts, web3, networkType } = useNetwork();
97 const { contracts, web3, networkType } = useNetwork();
101 const { wallet, walletType, walletHdPath } = useWallet();
98 const { wallet, walletType, walletHdPath } = useWallet();
102 const { syncInvites, getInvites } = usePointCache();
99 const { syncInvites, getInvites } = usePointCache();
@@ -427,21 +424,16 @@ export default function InviteEmail() {
427
424
428 return (
425 return (
429 <Grid gap={3}>
426 <Grid gap={3}>
430− <Grid.Item as={Grid} full>
427+ <Grid.Item full as={Flex} justify="end">
431− <Grid.Item as={Flex} cols={[1, 11]} align="center">
432− <MiniBackButton onClick={() => pop()} />
433− </Grid.Item>
434− <Grid.Item cols={[11, 13]} justifySelf="end">
435− {/* use hidden class instead of removing component from dom */}
428+ {/* use hidden class instead of removing component from dom */}
436− {/* in order to avoid janky reflow */}
429+ {/* in order to avoid janky reflow */}
437− <IconButton
430+ <IconButton
438− onClick={() => appendInput()}
431+ onClick={() => appendInput()}
439− disabled={!canAddInvite}
432+ disabled={!canAddInvite}
440− className={cn({ hidden: isDone })}
433+ className={cn({ hidden: isDone })}
441− solid>
434+ solid>
442− +
435+ +
443− </IconButton>
436+ </IconButton>
444− </Grid.Item>
445 </Grid.Item>
437 </Grid.Item>
446
438
447 {isDone && (
439 {isDone && (
src/views/IssueChild.js+7−10
@@ -6,23 +6,22 @@ import * as azimuth from 'azimuth-js';
6import * as ob from 'urbit-ob';
6import * as ob from 'urbit-ob';
7
7
8import { useNetwork } from 'store/network';
8import { useNetwork } from 'store/network';
9−import { useHistory } from 'store/history';
10import { usePointCache } from 'store/pointCache';
9import { usePointCache } from 'store/pointCache';
10+import { usePointCursor } from 'store/pointCursor';
11
11
12import * as need from 'lib/need';
12import * as need from 'lib/need';
13import { useAddressInput, usePointInput } from 'lib/useInputs';
13import { useAddressInput, usePointInput } from 'lib/useInputs';
14import useEthereumTransaction from 'lib/useEthereumTransaction';
14import useEthereumTransaction from 'lib/useEthereumTransaction';
15import { GAS_LIMITS } from 'lib/constants';
15import { GAS_LIMITS } from 'lib/constants';
16import patp2dec from 'lib/patp2dec';
16import patp2dec from 'lib/patp2dec';
17+import useLifecycle from 'lib/useLifecycle';
18+import { validateNameInNumberSet } from 'lib/validators';
19+import { getSpawnCandidate } from 'lib/child';
20+import { useLocalRouter } from 'lib/LocalRouter';
17
21
18import ViewHeader from 'components/ViewHeader';
22import ViewHeader from 'components/ViewHeader';
19−import MiniBackButton from 'components/MiniBackButton';
20import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
23import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
21import View from 'components/View';
24import View from 'components/View';
22−import useLifecycle from 'lib/useLifecycle';
23−import { usePointCursor } from 'store/pointCursor';
24−import { validateNameInNumberSet } from 'lib/validators';
25−import { getSpawnCandidate } from 'lib/child';
26
25
27function useIssueChild() {
26function useIssueChild() {
28 const { contracts } = useNetwork();
27 const { contracts } = useNetwork();
@@ -49,7 +48,7 @@ function useIssueChild() {
49}
48}
50
49
51export default function IssueChild() {
50export default function IssueChild() {
52− const { pop } = useHistory();
51+ const { pop } = useLocalRouter();
53 const { contracts } = useNetwork();
52 const { contracts } = useNetwork();
54 const { pointCursor } = usePointCursor();
53 const { pointCursor } = usePointCursor();
55
54
@@ -131,10 +130,8 @@ export default function IssueChild() {
131 });
130 });
132
131
133 return (
132 return (
134− <View inset>
133+ <View pop={pop} inset>
135 <Grid>
134 <Grid>
136− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
137−
138 <Grid.Item full as={ViewHeader}>
135 <Grid.Item full as={ViewHeader}>
139 Issue Child Point
136 Issue Child Point
140 </Grid.Item>
137 </Grid.Item>
src/views/Login.js+6−6
@@ -63,7 +63,7 @@ const walletTypeToViewName = walletType => {
63
63
64export default function Login() {
64export default function Login() {
65 // globals
65 // globals
66− const { push, replaceWith, names } = useHistory();
66+ const { pop, push, names } = useHistory();
67 const { contracts } = useNetwork();
67 const { contracts } = useNetwork();
68 const { wallet, walletType } = useWallet();
68 const { wallet, walletType } = useWallet();
69 const { pointCursor, setPointCursor } = usePointCursor();
69 const { pointCursor, setPointCursor } = usePointCursor();
@@ -75,10 +75,10 @@ export default function Login() {
75 walletTypeToViewName(walletType)
75 walletTypeToViewName(walletType)
76 );
76 );
77
77
78− const goToActivate = useCallback(
78+ const goToActivate = useCallback(() => push(names.ACTIVATE), [
79− () => replaceWith([{ key: names.LANDING }, { key: names.ACTIVATE }]),
79+ push,
80− [replaceWith, names]
80+ names.ACTIVATE,
81− );
81+ ]);
82
82
83 const goToPoints = useCallback(() => {
83 const goToPoints = useCallback(() => {
84 push(names.POINTS);
84 push(names.POINTS);
@@ -120,7 +120,7 @@ export default function Login() {
120 }, [contracts, pointCursor, setPointCursor, wallet, goToPoint, goToPoints]);
120 }, [contracts, pointCursor, setPointCursor, wallet, goToPoint, goToPoints]);
121
121
122 return (
122 return (
123− <View inset>
123+ <View pop={pop} inset>
124 <Grid>
124 <Grid>
125 <Grid.Item full as={Crumbs} routes={[{ text: 'Multipass' }]} />
125 <Grid.Item full as={Crumbs} routes={[{ text: 'Multipass' }]} />
126 <Grid.Item full as={H4} className="mt4">
126 <Grid.Item full as={H4} className="mt4">
src/views/Party/PartySetPoolSize.js+3−5
@@ -11,12 +11,11 @@ import * as need from 'lib/need';
11import { usePointInput, useNumberInput } from 'lib/useInputs';
11import { usePointInput, useNumberInput } from 'lib/useInputs';
12import useEthereumTransaction from 'lib/useEthereumTransaction';
12import useEthereumTransaction from 'lib/useEthereumTransaction';
13import { GAS_LIMITS } from 'lib/constants';
13import { GAS_LIMITS } from 'lib/constants';
14+import { useLocalRouter } from 'lib/LocalRouter';
14
15
15import ViewHeader from 'components/ViewHeader';
16import ViewHeader from 'components/ViewHeader';
16−import MiniBackButton from 'components/MiniBackButton';
17import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
17import InlineEthereumTransaction from 'components/InlineEthereumTransaction';
18import patp2dec from 'lib/patp2dec';
18import patp2dec from 'lib/patp2dec';
19−import { useHistory } from 'store/history';
20import View from 'components/View';
19import View from 'components/View';
21import useCurrentPermissions from 'lib/useCurrentPermissions';
20import useCurrentPermissions from 'lib/useCurrentPermissions';
22import WarningBox from 'components/WarningBox';
21import WarningBox from 'components/WarningBox';
@@ -46,7 +45,7 @@ function useSetPoolSize() {
46}
45}
47
46
48export default function PartySetPoolSize() {
47export default function PartySetPoolSize() {
49− const { pop } = useHistory();
48+ const { pop } = useLocalRouter();
50 const { contracts } = useNetwork();
49 const { contracts } = useNetwork();
51
50
52 const _contracts = need.contracts(contracts);
51 const _contracts = need.contracts(contracts);
@@ -88,9 +87,8 @@ export default function PartySetPoolSize() {
88 }, [poolOwner, construct, validPoint, validPoolSize, poolSize, unconstruct]);
87 }, [poolOwner, construct, validPoint, validPoolSize, poolSize, unconstruct]);
89
88
90 return (
89 return (
91− <View inset>
90+ <View pop={pop} inset>
92 <Grid>
91 <Grid>
93− <Grid.Item full as={MiniBackButton} onClick={() => pop()} />
94 <Grid.Item full as={ViewHeader}>
92 <Grid.Item full as={ViewHeader}>
95 Set Pool Size
93 Set Pool Size
96 </Grid.Item>
94 </Grid.Item>
src/views/Point.js+3−3
@@ -5,7 +5,6 @@ import { Grid } 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';
8−import { useHistory } from 'store/history';
9
8
10import View from 'components/View';
9import View from 'components/View';
11import Passport from 'components/Passport';
10import Passport from 'components/Passport';
@@ -15,9 +14,10 @@ import { matchBlinky } from 'components/Blinky';
15import useInvites from 'lib/useInvites';
14import useInvites from 'lib/useInvites';
16import { useSyncOwnedPoints } from 'lib/useSyncPoints';
15import { useSyncOwnedPoints } from 'lib/useSyncPoints';
17import useCurrentPermissions from 'lib/useCurrentPermissions';
16import useCurrentPermissions from 'lib/useCurrentPermissions';
17+import { useLocalRouter } from 'lib/LocalRouter';
18
18
19export default function Point() {
19export default function Point() {
20− const { push, names } = useHistory();
20+ const { pop, push, names } = useLocalRouter();
21 const { pointCursor } = usePointCursor();
21 const { pointCursor } = usePointCursor();
22
22
23 const point = need.point(pointCursor);
23 const point = need.point(pointCursor);
@@ -86,7 +86,7 @@ export default function Point() {
86 useSyncOwnedPoints([point]);
86 useSyncOwnedPoints([point]);
87
87
88 return (
88 return (
89− <View>
89+ <View pop={pop} inset>
90 <Passport point={Just(point)} />
90 <Passport point={Just(point)} />
91 <Grid className="pt2">
91 <Grid className="pt2">
92 {inviteButton}
92 {inviteButton}
src/views/Points.js+56−28
@@ -1,6 +1,7 @@
1−import React from 'react';
1+import React, { useCallback } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, H5, H1, HelpText, LinkButton, Flex } from 'indigo-react';
3import { Grid, H5, H1, HelpText, LinkButton, Flex } from 'indigo-react';
4+import { get } from 'lodash';
4
5
5import { useHistory } from 'store/history';
6import { useHistory } from 'store/history';
6import { useWallet } from 'store/wallet';
7import { useWallet } from 'store/wallet';
@@ -8,34 +9,33 @@ import { usePointCache } from 'store/pointCache';
8import { usePointCursor } from 'store/pointCursor';
9import { usePointCursor } from 'store/pointCursor';
9
10
10import * as need from 'lib/need';
11import * as need from 'lib/need';
11−import { ROUTE_NAMES } from 'lib/routeNames';
12import { isZeroAddress, abbreviateAddress } from 'lib/wallet';
12import { isZeroAddress, abbreviateAddress } from 'lib/wallet';
13import useIsEclipticOwner from 'lib/useIsEclipticOwner';
13import useIsEclipticOwner from 'lib/useIsEclipticOwner';
14import { useSyncKnownPoints } from 'lib/useSyncPoints';
14import { useSyncKnownPoints } from 'lib/useSyncPoints';
15
15
16import View from 'components/View';
16import View from 'components/View';
17−import FooterButton from 'components/FooterButton';
18import Blinky from 'components/Blinky';
17import Blinky from 'components/Blinky';
19import Passport from 'components/Passport';
18import Passport from 'components/Passport';
20import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
19import useRejectedIncomingPointTransfers from 'lib/useRejectedIncomingPointTransfers';
21import pluralize from 'lib/pluralize';
20import pluralize from 'lib/pluralize';
21+import Footer from 'components/Footer';
22+import { ForwardButton } from 'components/Buttons';
22
23
23−const getFromMaybe = (obj, key, defaultValue) =>
24+const maybeGetResult = (obj, key, defaultValue) =>
24 obj.matchWith({
25 obj.matchWith({
25 Nothing: () => defaultValue,
26 Nothing: () => defaultValue,
26 Just: p =>
27 Just: p =>
27 p.value.matchWith({
28 p.value.matchWith({
28− Ok: r => r.value[key],
29+ Ok: r => get(r.value, key, defaultValue),
29 Error: e => defaultValue,
30 Error: e => defaultValue,
30 }),
31 }),
31 });
32 });
32
33
33−const hasTransferProxy = (details, point) =>
34− !isZeroAddress(details.transferProxy);
34+const hasTransferProxy = details => !isZeroAddress(details.transferProxy);
35
35
36const PointList = function({ points, className, actions, ...rest }) {
36const PointList = function({ points, className, actions, ...rest }) {
37 const { setPointCursor } = usePointCursor();
37 const { setPointCursor } = usePointCursor();
38− const history = useHistory();
38+ const { push, names } = useHistory();
39
39
40 return (
40 return (
41 <Grid gap={3} className={className}>
41 <Grid gap={3} className={className}>
@@ -47,7 +47,7 @@ const PointList = function({ points, className, actions, ...rest }) {
47 className="clickable"
47 className="clickable"
48 onClick={() => {
48 onClick={() => {
49 setPointCursor(Just(point));
49 setPointCursor(Just(point));
50− history.push(history.names.POINT);
50+ push(names.POINT);
51 }}
51 }}
52 {...rest}
52 {...rest}
53 />
53 />
@@ -79,7 +79,7 @@ function ActionButtons({ actions = [] }) {
79
79
80export default function Points() {
80export default function Points() {
81 const { wallet } = useWallet();
81 const { wallet } = useWallet();
82− const history = useHistory();
82+ const { pop, push, names } = useHistory();
83 const { setPointCursor } = usePointCursor();
83 const { setPointCursor } = usePointCursor();
84 const { controlledPoints, getDetails } = usePointCache();
84 const { controlledPoints, getDetails } = usePointCache();
85 const isEclipticOwner = useIsEclipticOwner();
85 const isEclipticOwner = useIsEclipticOwner();
@@ -92,15 +92,15 @@ export default function Points() {
92
92
93 const loading = Nothing.hasInstance(controlledPoints);
93 const loading = Nothing.hasInstance(controlledPoints);
94
94
95− const ownedPoints = getFromMaybe(controlledPoints, 'ownedPoints', []);
95+ const ownedPoints = maybeGetResult(controlledPoints, 'ownedPoints', []);
96− const incomingPoints = getFromMaybe(
96+ const incomingPoints = maybeGetResult(
97 controlledPoints,
97 controlledPoints,
98 'incomingPoints',
98 'incomingPoints',
99 []
99 []
100 ).filter(point => !rejectedPoints.includes(point));
100 ).filter(point => !rejectedPoints.includes(point));
101− const managingPoints = getFromMaybe(controlledPoints, 'managingPoints', []);
101+ const managingPoints = maybeGetResult(controlledPoints, 'managingPoints', []);
102− const votingPoints = getFromMaybe(controlledPoints, 'votingPoints', []);
102+ const votingPoints = maybeGetResult(controlledPoints, 'votingPoints', []);
103− const spawningPoints = getFromMaybe(controlledPoints, 'spawningPoints', []);
103+ const spawningPoints = maybeGetResult(controlledPoints, 'spawningPoints', []);
104 const outgoingPoints = ownedPoints.filter(point =>
104 const outgoingPoints = ownedPoints.filter(point =>
105 getDetails(point).matchWith({
105 getDetails(point).matchWith({
106 Nothing: () => false,
106 Nothing: () => false,
@@ -115,7 +115,8 @@ export default function Points() {
115 ...spawningPoints,
115 ...spawningPoints,
116 ];
116 ];
117
117
118+ const displayEmptyState =
118− const isEmpty = incomingPoints.length === 0 && multipassPoints.length === 0;
119+ !loading && incomingPoints.length === 0 && multipassPoints.length === 0;
119
120
120 // sync display details for known points
121 // sync display details for known points
121 useSyncKnownPoints([
122 useSyncKnownPoints([
@@ -126,9 +127,19 @@ export default function Points() {
126 ...spawningPoints,
127 ...spawningPoints,
127 ]);
128 ]);
128
129
130+ const goCreateGalaxy = useCallback(() => push(names.CREATE_GALAXY), [
131+ names.CREATE_GALAXY,
132+ push,
133+ ]);
134+
135+ const goViewPoint = useCallback(() => push(names.VIEW_POINT), [
136+ names.VIEW_POINT,
137+ push,
138+ ]);
139+
129 return (
140 return (
130− <View>
141+ <View pop={pop} inset>
131− <Grid className="mt6">
142+ <Grid>
132 <Grid.Item full as={H1} className="f6 mono gray4 mb4">
143 <Grid.Item full as={H1} className="f6 mono gray4 mb4">
133 {abbreviateAddress(address)}
144 {abbreviateAddress(address)}
134 </Grid.Item>
145 </Grid.Item>
@@ -139,7 +150,7 @@ export default function Points() {
139 </Grid.Item>
150 </Grid.Item>
140 )}
151 )}
141
152
142− {isEmpty && (
153+ {displayEmptyState && (
143 <Grid.Item full as={HelpText} className="mt8 t-center">
154 <Grid.Item full as={HelpText} className="mt8 t-center">
144 No points to display. This wallet is not the owner or proxy for any
155 No points to display. This wallet is not the owner or proxy for any
145 points.
156 points.
@@ -166,7 +177,7 @@ export default function Points() {
166 text: 'Accept',
177 text: 'Accept',
167 onClick: () => {
178 onClick: () => {
168 setPointCursor(Just(point));
179 setPointCursor(Just(point));
169− history.push(history.names.ACCEPT_TRANSFER);
180+ push(names.ACCEPT_TRANSFER);
170 },
181 },
171 },
182 },
172 {
183 {
@@ -204,7 +215,7 @@ export default function Points() {
204 onClick: () => {
215 onClick: () => {
205 setPointCursor(Just(point));
216 setPointCursor(Just(point));
206 // TODO: deep linking to fix this duplicate route
217 // TODO: deep linking to fix this duplicate route
207− history.push(history.names.CANCEL_TRANSFER);
218+ push(names.CANCEL_TRANSFER);
208 },
219 },
209 },
220 },
210 ]}
221 ]}
@@ -224,13 +235,30 @@ export default function Points() {
224 </Grid.Item>
235 </Grid.Item>
225 )}
236 )}
226
237
238+ <Footer>
239+ <Grid>
240+ <Grid.Divider />
227− {isEclipticOwner && (
241+ {isEclipticOwner && (
228− <FooterButton
242+ <>
243+ <Grid.Item
244+ full
245+ as={ForwardButton}
229− detail="You have the authority to create a new Galaxy."
246+ detail="You have the authority to create a new Galaxy."
230− onClick={() => history.push(ROUTE_NAMES.CREATE_GALAXY)}>
247+ onClick={goCreateGalaxy}>
231− Create a galaxy
248+ Create a galaxy
232− </FooterButton>
249+ </Grid.Item>
233− )}
250+ <Grid.Divider />
251+ </>
252+ )}
253+ <Grid.Item
254+ full
255+ as={ForwardButton}
256+ detail="View a point"
257+ onClick={goViewPoint}>
258+ View a point
259+ </Grid.Item>
260+ </Grid>
261+ </Footer>
234 </Grid>
262 </Grid>
235 </View>
263 </View>
236 );
264 );
src/views/ViewPoint.js+27−19
@@ -1,19 +1,19 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2import { Just } from 'folktale/maybe';
2import { Just } from 'folktale/maybe';
3−import { Input, H1, P } from 'indigo-react';
3+import { Input, Grid, Text } from 'indigo-react';
4
4
5import { useHistory } from 'store/history';
5import { useHistory } from 'store/history';
6import { usePointCursor } from 'store/pointCursor';
6import { usePointCursor } from 'store/pointCursor';
7
7
8−import { ROUTE_NAMES } from 'lib/routeNames';
9import patp2dec from 'lib/patp2dec';
8import patp2dec from 'lib/patp2dec';
10import { usePointInput } from 'lib/useInputs';
9import { usePointInput } from 'lib/useInputs';
11
10
12import View from 'components/View';
11import View from 'components/View';
13import { ForwardButton } from 'components/Buttons';
12import { ForwardButton } from 'components/Buttons';
13+import ViewHeader from 'components/ViewHeader';
14
14
15export default function ViewPoint() {
15export default function ViewPoint() {
16− const history = useHistory();
16+ const { pop, popAndPush, names } = useHistory();
17 const { setPointCursor } = usePointCursor();
17 const { setPointCursor } = usePointCursor();
18 const [pointInput, { data: pointName, pass }] = usePointInput({
18 const [pointInput, { data: pointName, pass }] = usePointInput({
19 name: 'point',
19 name: 'point',
@@ -23,24 +23,32 @@ export default function ViewPoint() {
23 const disabled = !pass;
23 const disabled = !pass;
24 const goForward = useCallback(() => {
24 const goForward = useCallback(() => {
25 setPointCursor(Just(patp2dec(pointName)));
25 setPointCursor(Just(patp2dec(pointName)));
26− history.popAndPush(ROUTE_NAMES.POINT);
26+ popAndPush(names.POINT);
27− }, [setPointCursor, history, pointName]);
27+ }, [setPointCursor, pointName, popAndPush, names.POINT]);
28
28
29 return (
29 return (
30− <View>
30+ <View pop={pop} inset>
31+ <Grid>
32+ <Grid.Item full as={ViewHeader}>
31− <H1>View a Point</H1>
33+ View a Point
32−
34+ </Grid.Item>
35+
36+ <Grid.Item full as={Text}>
33− <P>Enter a point name to view its public information.</P>
37+ Enter a point name to view its public information.
34−
38+ </Grid.Item>
39+
35− <Input {...pointInput} onEnter={goForward} />
40+ <Grid.Item full as={Input} {...pointInput} onEnter={goForward} />
36−
41+
42+ <Grid.Item
43+ full
37− <ForwardButton
44+ as={ForwardButton}
38− className="mt3"
45+ className="mt3"
39− disabled={disabled}
46+ disabled={disabled}
40− onClick={goForward}
47+ onClick={goForward}
41− solid>
48+ solid>
42− Continue
49+ Continue
43− </ForwardButton>
50+ </Grid.Item>
51+ </Grid>
44 </View>
52 </View>
45 );
53 );
46}
54}