on master help sign in
feat: implement passport address, restart button, fix alignment for activate flow
commit8ca8b8ad4d91887217a04107887ec8cde208419c authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateMon 1 Jul 2019, 15:26 (7 years ago) parent7ac8cfe
13 files changed+163−76
src/components/Buttons.js+6 src/components/Passport.js+10−11 src/components/Steps.js+4−2 src/lib/useRouter.js+8−2 src/store/wallet.js−6 src/style/indigo-proposals.scss+4 src/views/Activate/ActivateCode.js+1−1 src/views/Activate/ActivatePassport.js+32−6 src/views/Activate/PassportDownload.js+4−6 src/views/Activate/PassportTransfer.js+79−37 src/views/Activate/PassportVerify.js+5−3 src/views/Activate/useActivateFlowState.js+9−1 src/views/Invite/InviteEmail.js+1−1
src/components/Buttons.js+6
@@ -13,3 +13,9 @@ export const OfflineButton = props => (
1313 Offline
1414 </OutButton>
1515);
16+
17+export const RestartButton = props => (
18+ <Button accessory="↺" solid {...props}>
19+ Restart
20+ </Button>
21+);
src/components/Passport.js+10−11
@@ -36,7 +36,7 @@ export default function Passport({
3636 point,
3737 className,
3838 ticket = null,
39− address = null,
39+ address = Maybe.Nothing(),
4040 mini = false,
4141}) {
4242 const birthday = usePointBirthday(point.getOrElse(null));
@@ -55,14 +55,12 @@ export default function Passport({
5555 });
5656
5757 const showTicket = ticket !== null;
58− const showValidTicket = !!ticket;
59− const visibleTicket = showValidTicket ? kValidTicket : kPendingTicket;
60− const visibleAddress =
61− address &&
62− `${address.matchWith({
63− Nothing: () => kEmptyHalfAddress,
64− Just: p => p.value.slice(0, kVisibleAddressCharacters),
65− })}${kBufferAddressCharacters}`;
58+ const isValidTiket = !!ticket;
59+ const visibleTicket = isValidTiket ? kValidTicket : kPendingTicket;
60+ const visibleAddress = `${address.matchWith({
61+ Nothing: () => kEmptyHalfAddress,
62+ Just: p => p.value.slice(0, kVisibleAddressCharacters),
63+ })}${kBufferAddressCharacters}`;
6664
6765 return (
6866 <Grid gap={4} className={cn('bg-black r8 p7', className)}>
@@ -87,8 +85,9 @@ export default function Passport({
8785 </Flex.Item>
8886 <Flex.Item
8987 as={Text}
90− className={cn('mono f6 f5-ns gray4 wrap', {
91− green3: showValidTicket,
88+ className={cn('mono f6 f5-ns wrap', {
89+ gray4: !isValidTiket,
90+ green3: isValidTiket,
9291 })}>
9392 {visibleTicket}
9493 </Flex.Item>
src/components/Steps.js+4−2
@@ -3,9 +3,11 @@ import cn from 'classnames';
33import { Text } from 'indigo-react';
44
55export default function Steps({ num = 1, total = 3, className }) {
6+ // NOTE: the 48px must match the `mt8` class on Passport for things to line
7+ // up nicely
68 return (
7− <Text className={cn('f6 gray3', className)}>
8− <Text className="f6 black">Step {num}</Text> of {total}
9+ <Text className={cn('f5 gray3', className)} style={{ height: '48px' }}>
10+ <Text className="f5 black">Step {num}</Text> of {total}
911 </Text>
1012 );
1113}
src/lib/useRouter.js+8−2
@@ -24,7 +24,6 @@ export default function useRouter({
2424 )}`
2525 );
2626 }
27−
2827 setRoutes(routes => [...routes, { key, data }]);
2928 },
3029 [setRoutes, views]
@@ -55,7 +54,13 @@ export default function useRouter({
5554 [setRoutes]
5655 );
5756 const peek = useCallback(() => last(routes), [routes]);
58− const replaceWith = useCallback(routes => setRoutes(routes), [setRoutes]);
57+ const replaceWith = useCallback(routes => setRoutes(() => routes), [
58+ setRoutes,
59+ ]);
60+ const reset = useCallback(() => setRoutes(initialRoutes), [
61+ setRoutes,
62+ initialRoutes,
63+ ]);
5964 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
6065 routes,
6166 ]);
@@ -92,6 +97,7 @@ export default function useRouter({
9297 popAndPush,
9398 pop,
9499 replaceWith,
100+ reset,
95101 peek,
96102 size,
97103 includes,
src/store/wallet.js−6
@@ -79,12 +79,6 @@ function _useWallet(
7979 urbitWallet.value.meta.passphrase
8080 );
8181
82− // manually set the public address
83− wallet.map(wal => {
84− wal.address = urbitWallet.value.ownership.keys.address;
85− return wal;
86− });
87−
8882 setWallet(wallet);
8983 } else {
9084 setWallet(Maybe.Nothing());
src/style/indigo-proposals.scss+4
@@ -35,3 +35,7 @@
3535.us-none {
3636 user-select: none;
3737}
38+
39+.auto-rows-min {
40+ grid-auto-rows: min-content;
41+}
src/views/Activate/ActivateCode.js+1−1
@@ -5,7 +5,7 @@ import { useTicketInput } from 'components/Inputs';
55import { Grid, Input, H4, ErrorText } from 'indigo-react';
66
77import View from 'components/View';
8−import { ForwardButton, OfflineButton } from 'components/Buttons';
8+import { ForwardButton } from 'components/Buttons';
99import Passport from 'components/Passport';
1010
1111import { useHistory } from 'store/history';
src/views/Activate/ActivatePassport.js+32−6
@@ -1,9 +1,9 @@
1−import React from 'react';
1+import React, { useMemo } from 'react';
22import cn from 'classnames';
33import Maybe from 'folktale/maybe';
44import { Grid } from 'indigo-react';
55
6−import { LocalRouterProvider } from 'lib/LocalRouter';
6+import { LocalRouterProvider, useLocalRouter } from 'lib/LocalRouter';
77
88import View from 'components/View';
99import Passport from 'components/Passport';
@@ -15,6 +15,11 @@ import useRouter from 'lib/useRouter';
1515import PassportDownload from './PassportDownload';
1616import PassportVerify from './PassportVerify';
1717import PassportTransfer from './PassportTransfer';
18+import {
19+ walletFromMnemonic,
20+ DEFAULT_HD_PATH,
21+ addressFromSecp256k1Public,
22+} from 'lib/wallet';
1823
1924const kPassportNames = {
2025 DOWNLOAD: 'DOWNLOAD',
@@ -29,8 +34,9 @@ const kPassportViews = {
2934};
3035
3136export default function ActivatePassport() {
32− const { derivedPoint, generated } = useActivateFlow();
33−
37+ const { derivedPoint, generated, derivedWallet } = useActivateFlow();
38+ // pull the reset function out of the 'activate' router
39+ const { reset: resetActivateRouter } = useLocalRouter();
3440 const { Route, ...router } = useRouter({
3541 names: kPassportNames,
3642 views: kPassportViews,
@@ -44,6 +50,21 @@ export default function ActivatePassport() {
4450 const leftHalf = useBreakpoints([false, false, 1]);
4551 const rightHalf = useBreakpoints([false, false, 2]);
4652
53+ // addresses are not derived until we set[Urbit]Wallet(), so do that inline
54+ const address = useMemo(
55+ () =>
56+ derivedWallet
57+ .chain(wallet =>
58+ walletFromMnemonic(
59+ wallet.ownership.seed,
60+ DEFAULT_HD_PATH,
61+ wallet.meta.passphrase
62+ )
63+ )
64+ .map(wallet => addressFromSecp256k1Public(wallet.publicKey)),
65+ [derivedWallet]
66+ );
67+
4768 return (
4869 <LocalRouterProvider value={router}>
4970 <View full={fullView}>
@@ -53,10 +74,15 @@ export default function ActivatePassport() {
5374 className={cn({ [`mt${marginTop}`]: marginTop })}
5475 point={derivedPoint}
5576 ticket={generated}
56− address={Maybe.Nothing()}
77+ address={address}
5778 />
5879 </Grid.Item>
59− <Grid.Item half={rightHalf} full={full} as={Route} />
80+ <Grid.Item
81+ half={rightHalf}
82+ full={full}
83+ as={Route}
84+ resetActivateRouter={resetActivateRouter}
85+ />
6086 </Grid>
6187 </View>
6288 </LocalRouterProvider>
src/views/Activate/PassportDownload.js+4−6
@@ -1,4 +1,5 @@
11import React, { useState, useEffect, useCallback } from 'react';
2+import cn from 'classnames';
23import Maybe from 'folktale/maybe';
34import { Grid, H4, P } from 'indigo-react';
45import PaperCollateralRenderer from 'PaperCollateralRenderer';
@@ -50,7 +51,7 @@ export default function PassportDownload({ className }) {
5051 const goToVerify = useCallback(() => push(names.VERIFY), [push, names]);
5152
5253 return (
53− <Grid gap={4} className={className}>
54+ <Grid className={cn(className, 'auto-rows-min')}>
5455 <Grid.Item full as={Steps} num={1} total={3} />
5556 <Grid.Item full as={H4}>
5657 Passport
@@ -76,12 +77,9 @@ export default function PassportDownload({ className }) {
7677 {!downloaded ? 'Download Passport' : 'Continue'}
7778 </Grid.Item>
7879 <PaperCollateralRenderer
79− className="super-hidden"
80+ className="abs super-hidden"
8081 wallet={{ [pointAsString]: wallet }}
81− callback={data => {
82− console.log(data);
83− setPaper(Maybe.Just(data));
84− }}
82+ callback={data => setPaper(Maybe.Just(data))}
8583 mode="REGISTRATION"
8684 />
8785 </Grid>
src/views/Activate/PassportTransfer.js+79−37
@@ -1,4 +1,5 @@
11import React, { useState, useCallback } from 'react';
2+import cn from 'classnames';
23import { Grid, H4, Text, ErrorText } from 'indigo-react';
34
45import { useNetwork } from 'store/network';
@@ -18,13 +19,19 @@ import LoadingBar from 'components/LoadingBar';
1819import Highlighted from 'components/Highlighted';
1920
2021import { useActivateFlow } from './ActivateFlow';
22+import { RestartButton } from 'components/Buttons';
2123
22−export default function PassportTransfer({ className }) {
24+export default function PassportTransfer({ className, resetActivateRouter }) {
2325 const { replaceWith, names } = useHistory();
2426 const { setUrbitWallet } = useWallet();
2527 const { setPointCursor } = usePointCursor();
2628 const { web3, contracts } = useNetwork();
27− const { derivedWallet, derivedPoint, inviteWallet } = useActivateFlow();
29+ const {
30+ derivedWallet,
31+ derivedPoint,
32+ inviteWallet,
33+ reset: resetActivateFlow,
34+ } = useActivateFlow();
2835 const [generalError, setGeneralError] = useState();
2936 const [{ label, progress }, setState] = useState({
3037 label: 'Verify Passport',
@@ -43,10 +50,21 @@ export default function PassportTransfer({ className }) {
4350 [replaceWith, names]
4451 );
4552
46− const goToRestart = useCallback(
47− () => replaceWith([{ key: names.LANDING }, { key: names.ACTIVATE }]),
48− [replaceWith, names]
49− );
53+ const goToRestart = useCallback(() => {
54+ // NOTE: because we're already on the ACTIVATE view in the history,
55+ // react (intelligently) doesn't trigger a re-render and that means the
56+ // current state of the two nested routers (activate & passport) stick
57+ // around unless we manually clear them.
58+ // we only need to clear activate because that will unmount the passport
59+ // router (which will be set to initialRoutes when mounted again)
60+
61+ // 1) replace history
62+ replaceWith([{ key: names.LANDING }, { key: names.ACTIVATE }]);
63+ // 2) reset local router
64+ resetActivateRouter();
65+ // 3) clear the state
66+ resetActivateFlow();
67+ }, [replaceWith, names, resetActivateRouter, resetActivateFlow]);
5068
5169 const handleUpdate = useCallback(
5270 ({ type, state, value }) => {
@@ -65,13 +83,15 @@ export default function PassportTransfer({ className }) {
6583 );
6684
6785 const claimPoint = useCallback(async () => {
68− const _inviteWallet = need.wallet(inviteWallet);
69− const _wallet = need.wallet(derivedWallet);
70− const _point = need.point(derivedPoint);
71− const _web3 = need.web3(web3);
72− const _contracts = need.contracts(contracts);
86+ setGeneralError(false);
7387
7488 try {
89+ const _inviteWallet = need.wallet(inviteWallet);
90+ const _wallet = need.wallet(derivedWallet);
91+ const _point = need.point(derivedPoint);
92+ const _web3 = need.web3(web3);
93+ const _contracts = need.contracts(contracts);
94+
7595 await claimPointFromInvite({
7696 inviteWallet: _inviteWallet,
7797 wallet: _wallet,
@@ -80,11 +100,13 @@ export default function PassportTransfer({ className }) {
80100 contracts: _contracts,
81101 onUpdate: handleUpdate,
82102 });
103+
83104 // set the global wallet
84105 setUrbitWallet(derivedWallet);
85106 setPointCursor(derivedPoint);
86− await timeout(3000);
107+
87108 // and redirect to login
109+ await timeout(3000);
88110 goToLogin();
89111 } catch (error) {
90112 // some generic error
@@ -106,13 +128,54 @@ export default function PassportTransfer({ className }) {
106128 claimPoint();
107129 });
108130
131+ const renderAdditionalInfo = () => {
132+ if (generalError) {
133+ return (
134+ <>
135+ <Grid.Item full className="mt8">
136+ <ErrorText>{generalError.message.toString()}</ErrorText>
137+ </Grid.Item>
138+ <Grid.Item
139+ full
140+ className="mt3"
141+ as={RestartButton}
142+ onClick={goToRestart}
143+ />
144+ </>
145+ );
146+ }
147+
148+ if (needFunds) {
149+ return (
150+ <Grid.Item full className="mt8">
151+ <Highlighted>
152+ The address {needFunds.address} needs at least{' '}
153+ {fromWei(needFunds.minBalance)} ETH and currently has{' '}
154+ {fromWei(needFunds.balance)} ETH. Waiting until the account has
155+ enough funds.
156+ </Highlighted>
157+ </Grid.Item>
158+ );
159+ }
160+
161+ if (progress < 100) {
162+ return (
163+ <Grid.Item full as={WarningBox} className="mt8">
164+ Never give your Master Ticket to anyone
165+ </Grid.Item>
166+ );
167+ }
168+
169+ return null;
170+ };
171+
109172 return (
110− <Grid gap={4} className={className}>
111− <Grid.Item full as={Steps} num={2} total={3} />
173+ <Grid className={cn(className, 'auto-rows-min')}>
174+ <Grid.Item full as={Steps} num={3} total={3} />
112175 <Grid.Item full as={H4}>
113176 {label}
114177 </Grid.Item>
115− <Grid.Item full as={Grid}>
178+ <Grid.Item full as={Grid} className="mt3" gap={3}>
116179 <Grid.Item full as={LoadingBar} progress={progress} />
117180 <Grid.Item full>
118181 <Text className="f5 green4">
@@ -122,28 +185,7 @@ export default function PassportTransfer({ className }) {
122185 </Grid.Item>
123186 </Grid.Item>
124187
125− {progress < 0 && (
126− <Grid.Item full as={WarningBox}>
127− Never give your Master Ticket to anyone
128− </Grid.Item>
129− )}
130−
131− {needFunds && (
132− <Grid.Item full>
133− <Highlighted>
134− The address {needFunds.address} needs at least{' '}
135− {fromWei(needFunds.minBalance)} ETH and currently has{' '}
136− {fromWei(needFunds.balance)} ETH. Waiting until the account has
137− enough funds.
138− </Highlighted>
139− </Grid.Item>
140− )}
141−
142− {generalError && (
143− <Grid.Item full>
144− <ErrorText>{generalError.message.toString()}</ErrorText>
145− </Grid.Item>
146− )}
188+ {renderAdditionalInfo()}
147189 </Grid>
148190 );
149191}
src/views/Activate/PassportVerify.js+5−3
@@ -1,5 +1,6 @@
11import React, { useCallback, useMemo } from 'react';
2−import { Grid, H4, P, Input, Text } from 'indigo-react';
2+import cn from 'classnames';
3+import { Grid, H4, Input, P } from 'indigo-react';
34
45import * as need from 'lib/need';
56import { useLocalRouter } from 'lib/LocalRouter';
@@ -33,12 +34,12 @@ export default function PassportVerify({ className }) {
3334 const { pass } = ticketInput;
3435
3536 return (
36− <Grid gap={4} className={className}>
37+ <Grid className={cn(className, 'auto-rows-min')}>
3738 <Grid.Item full as={Steps} num={2} total={3} />
3839 <Grid.Item full as={H4}>
3940 Verify Passport
4041 </Grid.Item>
41− <Grid.Item full as={Text} className="f5">
42+ <Grid.Item full as={P}>
4243 After you download your passport, verify your custody. Your passport
4344 should be a folder of image files. One of them is your Master Ticket.
4445 Open it and enter the 4 word phrase below (with hyphens).
@@ -46,6 +47,7 @@ export default function PassportVerify({ className }) {
4647 <Grid.Item full as={Input} {...ticketInput} />
4748 <Grid.Item
4849 full
50+ className="mt3"
4951 as={ForwardButton}
5052 disabled={!pass}
5153 onClick={goToTransfer}
src/views/Activate/useActivateFlowState.js+9−1
@@ -1,4 +1,4 @@
1−import { useState } from 'react';
1+import { useCallback, useState } from 'react';
22import Maybe from 'folktale/maybe';
33
44export default function useActivateFlowState() {
@@ -7,6 +7,13 @@ export default function useActivateFlowState() {
77 const [derivedPoint, setDerivedPoint] = useState(Maybe.Nothing());
88 const [generated, setGenerated] = useState(false);
99
10+ const reset = useCallback(() => {
11+ setDerivedWallet(Maybe.Nothing());
12+ setInviteWallet(Maybe.Nothing());
13+ setDerivedPoint(Maybe.Nothing());
14+ setGenerated(false);
15+ }, [setDerivedWallet, setInviteWallet, setDerivedPoint, setGenerated]);
16+
1017 return {
1118 derivedWallet,
1219 setDerivedWallet,
@@ -16,5 +23,6 @@ export default function useActivateFlowState() {
1623 setDerivedPoint,
1724 generated,
1825 setGenerated,
26+ reset,
1927 };
2028}
src/views/Invite/InviteEmail.js+1−1
@@ -114,7 +114,7 @@ export default function InviteEmail() {
114114 inputConfigs,
115115 { append: appendInput, removeAt: removeInputAt },
116116 ] = useArray(
117− [buildInputConfig({ label: 'Email Address' })],
117+ [buildInputConfig({ label: 'Email Address', autoFocus: true })],
118118 buildInputConfig
119119 );
120120