on master help sign in
feat: implement disclaimer, passport download & verify
commit4b253295cfc9ea0f40fcd53f8d05a081b32cfc00 authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateThu 27 Jun 2019, 19:26 (7 years ago) parent30fffab
24 files changed+441−144
src/components/Inputs.js+11−8 src/components/LoadingBar.js+19 src/components/Passport.js+13−14 src/components/Steps.js+11 src/components/WarningBox.js+11 src/indigo-react/components/Grid.js+13−2 src/indigo-react/components/Typography.js+1−1 src/indigo-react/indigo.css+13−26 src/indigo-react/lib/useForm.js+7−6 src/lib/MediaQuery.js+24 src/lib/useBreakpoints.js+5 src/lib/useMedia.js+31 src/lib/useRouter.js+2 src/lib/validators.js+7 src/store/Provider.js+2 src/views/Activate/ActivateCode.js+8−18 src/views/Activate/ActivateDisclaimer.js+3−4 src/views/Activate/ActivatePassport.js+45−63 src/views/Activate/PassportDownload.js+89 src/views/Activate/PassportTransfer.js+64 src/views/Activate/PassportVerify.js+57 src/views/Activate/useActivateFlowState.js+3 src/views/Invite.js+1−1 src/views/Invite/InviteEmail.js+1−1
src/components/Inputs.js+11−8
@@ -1,4 +1,4 @@
1−import React, { useState } from 'react';
1+import React, { useState, useMemo } from 'react';
22import * as bip39 from 'bip39';
33
44import InputSigil from 'components/InputSigil';
@@ -13,6 +13,7 @@ import {
1313} from 'lib/validators';
1414import { prependSig } from 'lib/transformers';
1515import useForm from 'indigo-react/lib/useForm';
16+import { AccessoryIcon } from 'indigo-react';
1617
1718// const RequiredInput = advancedInput({
1819// WrappedComponent: Input,
@@ -87,26 +88,29 @@ export function useHdPathInput(props) {
8788const kTicketValidators = [validateTicket, validateNotEmpty];
8889const kTicketTransformers = [prependSig];
8990//TODO needs to be fancier, displaying sig and dashes instead of •ing all
90−export function useTicketInput({ initialValue = '~', ...rest }) {
91− return firstOf(
91+export function useTicketInput({ validators = [], ...rest }) {
92+ const input = firstOf(
9293 useForm([
9394 {
9495 type: 'password',
9596 label: 'Ticket',
96− placeholder: '~master-ticket',
97− validators: kTicketValidators,
97+ placeholder: '~ragtyd-modwen-bostec-hinsep',
98+ validators: useMemo(() => [...validators, ...kTicketValidators], [
99+ validators,
100+ ]),
98101 transformers: kTicketTransformers,
99102 mono: true,
100− initialValue,
101103 ...rest,
102104 },
103105 ])
104106 );
107+
108+ return { ...input, accessory: input.pass ? <AccessoryIcon.Success /> : null };
105109}
106110
107111const kPointValidators = [validatePoint, validateNotEmpty];
108112const kPointTransformers = [prependSig];
109−export function usePointInput({ initialValue = '~', ...rest }) {
113+export function usePointInput(rest) {
110114 const [lastValidPoint, setLastValidPoint] = useState('');
111115 const input = firstOf(
112116 useForm([
@@ -117,7 +121,6 @@ export function usePointInput({ initialValue = '~', ...rest }) {
117121 validators: kPointValidators,
118122 transformers: kPointTransformers,
119123 mono: true,
120− initialValue,
121124 onValue: setLastValidPoint,
122125 ...rest,
123126 },
src/components/LoadingBar.jsnew file+19
@@ -0,0 +1,19 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+// progrss is [0, 1]
5+export default function LoadingBar({ className, progress = 1 }) {
6+ return (
7+ <div className={cn('rel bg-gray2', className)} style={{ height: '4px' }}>
8+ <div
9+ className="abs bg-green3"
10+ style={{
11+ top: 0,
12+ bottom: 0,
13+ left: 0,
14+ width: `${progress * 100.0}%`,
15+ }}
16+ />
17+ </div>
18+ );
19+}
src/components/Passport.js+13−14
@@ -16,21 +16,20 @@ const buildDate = char =>
1616const kDateA = buildDate(kLoadingCharacter);
1717const kDateB = buildDate(kInterstitialCharacter);
1818
19−const kTicketCharacterLength = 40;
20−const kPendingTicket = times(
21− kTicketCharacterLength,
22− () => kLoadingCharacter
19+const kTicketCharacterLength = 30;
20+const kTicket = times(kTicketCharacterLength, i =>
21+ i % 2 === 0 ? kLoadingCharacter : kInterstitialCharacter
2322).join('');
24−const kValidTicket =
25− times(kTicketCharacterLength, () => kLoadingCharacter).join('') + '◆';
23+const kPendingTicket = kTicket;
24+const kValidTicket = `${kTicket} ◆`;
2625
2726const kVisibleAddressCharacters = 20;
2827const kEmptyHalfAddress = times(kVisibleAddressCharacters, () => '<').join('');
29−const bufferAddressCharacters = times(40, () => '<').join('');
28+const kBufferAddressCharacters = times(40, () => '<').join('');
3029
3130/**
3231 * point is Maybe<number>
33− * ticket is boolean
32+ * ticket is null | boolean
3433 * address is null | Maybe<string>
3534 */
3635export default function Passport({
@@ -63,7 +62,7 @@ export default function Passport({
6362 `${address.matchWith({
6463 Nothing: () => kEmptyHalfAddress,
6564 Just: p => p.value.slice(0, kVisibleAddressCharacters),
66− })}${bufferAddressCharacters}`;
65+ })}${kBufferAddressCharacters}`;
6766
6867 return (
6968 <Grid gap={4} className={cn('bg-black r8 p7', className)}>
@@ -73,27 +72,27 @@ export default function Passport({
7372 <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
7473 <Flex.Item className="mono white f5">{visualName}</Flex.Item>
7574 <Flex.Item as={Flex} col>
76− <Flex.Item as={Text} className="mono gray4 uppercase">
75+ <Flex.Item as={Text} className="mono f6 f5-ns gray4 uppercase">
7776 Birth Time
7877 </Flex.Item>
79− <Flex.Item as={Text} className="mono gray4">
78+ <Flex.Item as={Text} className="mono f6 f5-ns gray4 ">
8079 {visualBirthday}
8180 </Flex.Item>
8281 </Flex.Item>
8382 </Grid.Item>
8483 {!mini && showTicket && (
8584 <Grid.Item full className="mv3" as={Flex} col>
86− <Flex.Item as={Text} className="mono gray4 uppercase">
85+ <Flex.Item as={Text} className="mono f6 f5-ns gray4 uppercase">
8786 Master Ticket
8887 </Flex.Item>
8988 <Flex.Item
9089 as={Text}
91− className={cn('mono gray4', {
90+ className={cn('mono f6 f5-ns gray4 wrap', {
9291 green3: showValidTicket,
9392 })}>
9493 {visibleTicket}
9594 </Flex.Item>
96− <Flex.Item as={Text} className="mono gray4">
95+ <Flex.Item as={Text} className="mono f6 f5-ns gray4 wrap">
9796 {visibleAddress}
9897 </Flex.Item>
9998 </Grid.Item>
src/components/Steps.jsnew file+11
@@ -0,0 +1,11 @@
1+import React from 'react';
2+import cn from 'classnames';
3+import { Text } from 'indigo-react';
4+
5+export default function Steps({ num = 1, total = 3, className }) {
6+ return (
7+ <Text className={cn('f6 gray3', className)}>
8+ <Text className="f6 black">Step {num}</Text> of {total}
9+ </Text>
10+ );
11+}
src/components/WarningBox.jsnew file+11
@@ -0,0 +1,11 @@
1+import React from 'react';
2+import cn from 'classnames';
3+import { Flex, Text } from 'indigo-react';
4+
5+export default function WarningBox({ className, children }) {
6+ return (
7+ <Flex className={cn('bg-red1 pv3 ph4', className)} align="center">
8+ <Text className="f5 red3">{children}</Text>
9+ </Flex>
10+ );
11+}
src/indigo-react/components/Grid.js+13−2
@@ -1,9 +1,20 @@
11import React from 'react';
22import cn from 'classnames';
33
4−function Grid({ as: As = 'div', gap = 0, className, ...rest }) {
4+function Grid({ as: As = 'div', gap = 0, align, justify, className, ...rest }) {
55 return (
6− <As className={cn('grid12', gap && `gap${gap}`, className)} {...rest} />
6+ <As
7+ className={cn(
8+ 'grid12',
9+ gap && `gap${gap}`,
10+ {
11+ [`justify-${justify}`]: justify,
12+ [`align-${align}`]: align,
13+ },
14+ className
15+ )}
16+ {...rest}
17+ />
718 );
819}
920
src/indigo-react/components/Typography.js+1−1
@@ -17,6 +17,6 @@ export const P = typeWith('p');
1717export const B = typeWith('b');
1818
1919export const Breadcrumb = typeWith('span', 'f6');
20−export const Text = typeWith('span', 'f5');
20+export const Text = typeWith('span');
2121export const HelpText = typeWith('span', 'f6 gray4');
2222export const ErrorText = typeWith('span', 'f6 red3');
src/indigo-react/indigo.css+13−26
@@ -13,7 +13,7 @@
1313 * git+https://github.com/urbit/Indigo.git
1414 * Bugs: https://github.com/urbit/Indigo/issues
1515 * License: MIT
16− * Timestamp: Thu Jun 27 2019 14:14:25 GMT-0700 (Pacific Daylight Time)
16+ * Timestamp: Thu Jun 27 2019 18:03:00 GMT-0700 (Pacific Daylight Time)
1717 *******************************************************************************/
1818/**********************
1919Color variables
@@ -734,13 +734,18 @@ caption,
734734.pre {
735735 white-space: pre; }
736736
737+.wrap {
738+ word-break: break-all; }
739+
737740@media only screen and (min-width: 550px) {
738741 .ws-normal-ns {
739742 white-space: normal; }
740743 .nowrap-ns {
741744 white-space: nowrap; }
742745 .pre-ns {
743− white-space: pre; } }
746+ white-space: pre; }
747+ .wrap-ns {
748+ word-break: break-all; } }
744749
745750@media only screen and (min-width: 750px) and (max-width: 960px) {
746751 .ws-normal-md {
@@ -748,7 +753,9 @@ caption,
748753 .nowrap-md {
749754 white-space: nowrap; }
750755 .pre-md {
751− white-space: pre; } }
756+ white-space: pre; }
757+ .wrap-md {
758+ word-break: break-all; } }
752759
753760@media only screen and (min-width: 960px) {
754761 .ws-normal-lg {
@@ -756,7 +763,9 @@ caption,
756763 .nowrap-lg {
757764 white-space: nowrap; }
758765 .pre-lg {
759− white-space: pre; } }
766+ white-space: pre; }
767+ .wrap-lg {
768+ word-break: break-all; } }
760769
761770/*******************************************************************************
762771 Border Radius
@@ -1531,43 +1540,33 @@ caption,
15311540 flex-wrap: wrap; }
15321541
15331542.align-start {
1534− display: flex;
15351543 align-items: flex-start; }
15361544
15371545.align-end {
1538− display: flex;
15391546 align-items: flex-end; }
15401547
15411548.align-center {
1542− display: flex;
15431549 align-items: center; }
15441550
15451551.align-baseline {
1546− display: flex;
15471552 align-items: baseline; }
15481553
15491554.align-stretch {
1550− display: flex;
15511555 align-items: stretch; }
15521556
15531557.justify-start {
1554− display: flex;
15551558 justify-content: flex-start; }
15561559
15571560.justify-end {
1558− display: flex;
15591561 justify-content: flex-end; }
15601562
15611563.justify-center {
1562− display: flex;
15631564 justify-content: center; }
15641565
15651566.justify-between {
1566− display: flex;
15671567 justify-content: space-between; }
15681568
15691569.justify-around {
1570− display: flex;
15711570 justify-content: space-around; }
15721571
15731572.flex-center {
@@ -1627,16 +1626,12 @@ caption,
16271626 .flex-ns {
16281627 display: flex; }
16291628 .flex-row-ns {
1630− display: flex;
16311629 flex-direction: row; }
16321630 .flex-row-r-ns {
1633− display: flex;
16341631 flex-direction: row-reverse; }
16351632 .flex-col-ns {
1636− display: flex;
16371633 flex-direction: column; }
16381634 .flex-col-r-ns {
1639− display: flex;
16401635 flex-direction: column-reverse; }
16411636 .flex-wrap-ns {
16421637 flex-wrap: wrap; }
@@ -1700,16 +1695,12 @@ caption,
17001695 .flex-md {
17011696 display: flex; }
17021697 .flex-row-md {
1703− display: flex;
17041698 flex-direction: row; }
17051699 .flex-row-r-md {
1706− display: flex;
17071700 flex-direction: row-reverse; }
17081701 .flex-col-md {
1709− display: flex;
17101702 flex-direction: column; }
17111703 .flex-col-r-md {
1712− display: flex;
17131704 flex-direction: column-reverse; }
17141705 .flex-wrap-md {
17151706 flex-wrap: wrap; }
@@ -1773,16 +1764,12 @@ caption,
17731764 .flex-lg {
17741765 display: flex; }
17751766 .flex-row-lg {
1776− display: flex;
17771767 flex-direction: row; }
17781768 .flex-row-r-lg {
1779− display: flex;
17801769 flex-direction: row-reverse; }
17811770 .flex-col-lg {
1782− display: flex;
17831771 flex-direction: column; }
17841772 .flex-col-r-lg {
1785− display: flex;
17861773 flex-direction: column-reverse; }
17871774 .flex-wrap-lg {
17881775 flex-wrap: wrap; }
src/indigo-react/lib/useForm.js+7−6
@@ -29,11 +29,7 @@ export default function useForm(configs = []) {
2929
3030 // track values
3131 const [values, _setValues] = useState(() =>
32− defaultsFor(configs, config => config.initialValue)
33− );
34− const setValue = useCallback(
35− (name, value) => _setValues(values => ({ ...values, [name]: value })),
36− [_setValues]
32+ defaultsFor(configs, config => config.initialValue || '')
3733 );
3834 // ^ NB(shrugs): because we're not syncing the initialValue of _additional_
3935 // configs that are added, we won't be able to set an initialValue for a
@@ -42,6 +38,11 @@ export default function useForm(configs = []) {
4238 // but for dynamically added input configs, we won't get validation, etc
4339 // until there's a traditional state update.
4440
41+ const setValue = useCallback(
42+ (name, value) => _setValues(values => ({ ...values, [name]: value })),
43+ [_setValues]
44+ );
45+
4546 // track focused states
4647 const [focused, setFocused] = useSetState(() =>
4748 defaultsFor(configs, config => config.autoFocus && !config.disabled)
@@ -165,7 +166,7 @@ export default function useForm(configs = []) {
165166 ...rest,
166167 // dom properties below:
167168 bind: {
168− value: values[name] || initialValue,
169+ value: values[name] || initialValue || '',
169170 checked: !!values[name],
170171 onChange: onChange(name),
171172 onFocus: onFocus(name),
src/lib/MediaQuery.jsnew file+24
@@ -0,0 +1,24 @@
1+import React from 'react';
2+
3+import { createContext, useContext } from 'react';
4+import useMedia from './useMedia';
5+
6+// sync with //indigo/indigo/css/src/lib/_breakpoints.scss
7+const kBreakpoints = [
8+ '(min-width: 550px)', // ns
9+ '(min-width: 750px)', // md
10+ '(min-width: 960px)', // lg
11+];
12+
13+export const MediaQueryContext = createContext(null);
14+// provide the index to consumers so they can easily index an array
15+export const MediaQueryProvider = ({ children }) => (
16+ <MediaQueryContext.Provider value={useMedia(kBreakpoints, [0, 1, 2], 0)}>
17+ {children}
18+ </MediaQueryContext.Provider>
19+);
20+
21+// Hook version
22+export function useMediaQuery() {
23+ return useContext(MediaQueryContext);
24+}
src/lib/useBreakpoints.jsnew file+5
@@ -0,0 +1,5 @@
1+import { useMediaQuery } from './MediaQuery';
2+
3+export default function useBreakpoints(values = [null, null, null]) {
4+ return values[useMediaQuery()];
5+}
src/lib/useMedia.jsnew file+31
@@ -0,0 +1,31 @@
1+import { useCallback, useMemo, useState, useEffect } from 'react';
2+
3+// via: https://usehooks.com/useMedia/
4+// NB(shrugs): we reverse the order because we want to be 'mobile first'
5+// and therefore want the breakpoint that is the most "specific"
6+export default function useMedia(queries, values, defaultValue) {
7+ const reverseValues = useMemo(() => values.reverse(), [values]);
8+ const mediaQueryLists = useMemo(
9+ () => queries.reverse().map(q => window.matchMedia(q)),
10+ [queries]
11+ );
12+
13+ const getValue = useCallback(() => {
14+ const index = mediaQueryLists.findIndex(mql => mql.matches);
15+
16+ return typeof reverseValues[index] !== 'undefined'
17+ ? reverseValues[index]
18+ : defaultValue;
19+ }, [mediaQueryLists, reverseValues, defaultValue]);
20+
21+ const [value, setValue] = useState(getValue);
22+
23+ useEffect(() => {
24+ const handler = () => setValue(getValue);
25+ mediaQueryLists.forEach(mql => mql.addListener(handler));
26+
27+ return () => mediaQueryLists.forEach(mql => mql.removeListener(handler));
28+ }, [mediaQueryLists, getValue]);
29+
30+ return value;
31+}
src/lib/useRouter.js+2
@@ -55,6 +55,7 @@ export default function useRouter({
5555 [setRoutes]
5656 );
5757 const peek = useCallback(() => last(routes), [routes]);
58+ const replaceWith = useCallback(routes => setRoutes(routes), [setRoutes]);
5859 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
5960 routes,
6061 ]);
@@ -90,6 +91,7 @@ export default function useRouter({
9091 push,
9192 popAndPush,
9293 pop,
94+ replaceWith,
9395 peek,
9496 size,
9597 includes,
src/lib/validators.js+7
@@ -185,3 +185,10 @@ export const validateInt = m =>
185185 },
186186 error: 'This is not a valid number',
187187 });
188+
189+export const validateExactly = (value, error) => m =>
190+ simpleValidatorWrapper({
191+ prevMessage: m,
192+ validator: d => d === value,
193+ error,
194+ });
src/store/Provider.js+2
@@ -8,6 +8,7 @@ import { WalletProvider } from './wallet';
88import { PointCursorProvider } from './pointCursor';
99import { PointCacheProvider } from './pointCache';
1010import { TxnCursorProvider } from './txnCursor';
11+import { MediaQueryProvider } from 'lib/MediaQuery';
1112
1213// nest all of the providers within each other to avoid hella depth
1314export default nest([
@@ -19,4 +20,5 @@ export default nest([
1920 PointCursorProvider,
2021 PointCacheProvider,
2122 TxnCursorProvider,
23+ MediaQueryProvider,
2224]);
src/views/Activate/ActivateCode.js+8−18
@@ -68,32 +68,22 @@ export default function ActivateCode() {
6868 );
6969
7070 //TODO isn't all this accessible in the ownership object?
71− const inviteWallet = walletFromMnemonic(
72− seed,
73− DEFAULT_HD_PATH
74− ).matchWith({
75− Just: w => w.value,
76− Nothing: () => {
77− setGeneralError('Derivation Error: invalid derived seed.');
78− throw new Error();
79− },
80− });
71+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
72+ const _inviteWallet = need.wallet(inviteWallet);
8173
8274 const incoming = await azimuth.azimuth.getTransferringFor(
8375 _contracts,
84− inviteWallet.address
76+ _inviteWallet.address
8577 );
8678
8779 let realPoint = Maybe.Nothing();
88− let realWallet = Maybe.Nothing();
80+ let wallet = Maybe.Nothing();
8981
9082 if (incoming.length > 0) {
91− let pointNum = parseInt(incoming[0], 10);
83+ const pointNum = parseInt(incoming[0], 10);
9284 realPoint = Maybe.Just(pointNum);
85+ wallet = Maybe.Just(await generateWallet(pointNum));
9386
94− const genWallet = await generateWallet(pointNum);
95−
96− realWallet = Maybe.Just(genWallet);
9787 if (incoming.length > 1) {
9888 setGeneralError(
9989 'This invite code has multiple points available.\n' +
@@ -108,8 +98,8 @@ export default function ActivateCode() {
10898 }
10999
110100 setDerivedPoint(realPoint);
111− setDerivedWallet(realWallet);
112− setInviteWallet(Maybe.Just(inviteWallet));
101+ setDerivedWallet(wallet);
102+ setInviteWallet(inviteWallet);
113103 setDeriving(false);
114104 })();
115105 } else {
src/views/Activate/ActivateDisclaimer.js+3−4
@@ -5,6 +5,7 @@ import View from 'components/View';
55import { useCheckboxInput } from 'components/Inputs';
66import { ForwardButton } from 'components/Buttons';
77import { useLocalRouter } from 'lib/LocalRouter';
8+import WarningBox from 'components/WarningBox';
89
910export default function ActivateDisclaimer() {
1011 const { push, names } = useLocalRouter();
@@ -63,10 +64,8 @@ export default function ActivateDisclaimer() {
6364 problems that may arise in connection with your wallet.
6465 </P>
6566 </Grid.Item>
66− <Grid.Item as={Flex} className="bg-red1 pv3 ph4" align="center" full>
67− <Text className="red3 fw-bold">
68− Warning: We Can't Restore of Reset Your Master Ticket
69− </Text>
67+ <Grid.Item full as={WarningBox}>
68+ Warning: We Can't Restore of Reset Your Master Ticket
7069 </Grid.Item>
7170 <Grid.Item as={CheckboxInput} {...checkboxInput} full />
7271 <Grid.Item
src/views/Activate/ActivatePassport.js+45−63
@@ -1,82 +1,64 @@
1−import React, { useCallback, useState } from 'react';
1+import React from 'react';
2+import cn from 'classnames';
23import Maybe from 'folktale/maybe';
3−import { Grid, H4, P } from 'indigo-react';
4−import PaperCollateralRenderer from 'PaperCollateralRenderer';
4+import { Grid } from 'indigo-react';
55
6−import * as need from 'lib/need';
7−import { useLocalRouter } from 'lib/LocalRouter';
6+import { LocalRouterProvider } from 'lib/LocalRouter';
87
98import View from 'components/View';
10−import { ForwardButton, DownloadButton } from 'components/Buttons';
119import Passport from 'components/Passport';
1210
1311import { useActivateFlow } from './ActivateFlow';
14−import { downloadWallet } from 'lib/invite';
12+import useBreakpoints from 'lib/useBreakpoints';
13+import useRouter from 'lib/useRouter';
1514
16−export default function ActivatePassport() {
17− const { push, names } = useLocalRouter();
18− const { derivedPoint, derivedWallet } = useActivateFlow();
19− // const point = need.point(derivedPoint);
20− const wallet = need.wallet(derivedWallet);
15+import PassportDownload from './PassportDownload';
16+import PassportVerify from './PassportVerify';
17+import PassportTransfer from './PassportTransfer';
2118
22− const [paper, setPaper] = useState(Maybe.Nothing());
19+const kPassportNames = {
20+ DOWNLOAD: 'DOWNLOAD',
21+ VERIFY: 'VERIFY',
22+ TRANSFER: 'TRANSFER',
23+};
2324
24− const goToPassport = useCallback(() => push(names.PASSPORT), [push, names]);
25+const kPassportViews = {
26+ [kPassportNames.DOWNLOAD]: PassportDownload,
27+ [kPassportNames.VERIFY]: PassportVerify,
28+ [kPassportNames.TRANSFER]: PassportTransfer,
29+};
2530
26− const pointAsString = derivedPoint.matchWith({
27− Nothing: () => '',
28− Just: p => p.value.toFixed(),
29− });
31+export default function ActivatePassport() {
32+ const { derivedPoint, generated } = useActivateFlow();
3033
31− const generated = paper.matchWith({
32− Nothing: () => false,
33− Just: () => true,
34+ const { Route, ...router } = useRouter({
35+ names: kPassportNames,
36+ views: kPassportViews,
37+ initialRoutes: [{ key: kPassportNames.DOWNLOAD }],
3438 });
3539
36− const download = useCallback(() => downloadWallet(paper.getOrElse([])), [
37− paper,
38− ]);
40+ const fullView = useBreakpoints([false, false, true]);
41+ const gap = useBreakpoints([4, 4, 7]);
42+ const marginTop = useBreakpoints([false, false, 8]);
43+ const full = useBreakpoints([true, true, false]);
44+ const leftHalf = useBreakpoints([false, false, 1]);
45+ const rightHalf = useBreakpoints([false, false, 2]);
3946
4047 return (
41− <View.Full>
42− <Grid gap={10} className="mt8 mb10" align="center">
43− <Grid.Item half={1} alignSelf="center">
44− <Passport point={derivedPoint} ticket={generated} />
45− </Grid.Item>
46− <Grid.Item half={2} as={Grid} gap={5} alignSelf="center">
47− <Grid.Item full>Step 1 of 3</Grid.Item>
48− <Grid.Item full as={H4}>
49− Passport
50− </Grid.Item>
51− <Grid.Item full as={P}>
52− After you’ve downloaded your passport, back up the ticket manually
53− or store on a trusted device.
54− </Grid.Item>
55− <Grid.Item full as={P}>
56− What is digital identity? A passport is your digital identity. You
57− will use your passport to access your true computer, send payments,
58− and administer your identity. So naturally, you must keep this
59− secure.
60− </Grid.Item>
61− <Grid.Item
62− full
63− as={DownloadButton}
64− disabled={!generated}
65− onClick={download}
66− solid>
67− Download Passport
48+ <LocalRouterProvider value={router}>
49+ <View full={fullView}>
50+ <Grid gap={gap} className="mt8 mb10">
51+ <Grid.Item half={leftHalf} full={full}>
52+ <Passport
53+ className={cn({ [`mt${marginTop}`]: marginTop })}
54+ point={derivedPoint}
55+ ticket={generated}
56+ address={Maybe.Nothing()}
57+ />
6858 </Grid.Item>
69− </Grid.Item>
70− </Grid>
71− <PaperCollateralRenderer
72− className="super-hidden"
73− wallet={{ [pointAsString]: wallet }}
74− callback={data => {
75− console.log(data);
76− setPaper(Maybe.Just(data));
77− }}
78− mode="REGISTRATION"
79− />
80− </View.Full>
59+ <Grid.Item half={rightHalf} full={full} as={Route} />
60+ </Grid>
61+ </View>
62+ </LocalRouterProvider>
8163 );
8264}
src/views/Activate/PassportDownload.jsnew file+89
@@ -0,0 +1,89 @@
1+import React, { useState, useEffect, useCallback } from 'react';
2+import Maybe from 'folktale/maybe';
3+import { Grid, H4, P } from 'indigo-react';
4+import PaperCollateralRenderer from 'PaperCollateralRenderer';
5+
6+import * as need from 'lib/need';
7+
8+import { useActivateFlow } from './ActivateFlow';
9+import { downloadWallet } from 'lib/invite';
10+import { DownloadButton, ForwardButton } from 'components/Buttons';
11+import { useLocalRouter } from 'lib/LocalRouter';
12+import Steps from 'components/Steps';
13+
14+export default function PassportDownload({ className }) {
15+ const {
16+ derivedPoint,
17+ derivedWallet,
18+ generated,
19+ setGenerated,
20+ } = useActivateFlow();
21+ const { push, names } = useLocalRouter();
22+ // const point = need.point(derivedPoint);
23+ const wallet = need.wallet(derivedWallet);
24+
25+ const [paper, setPaper] = useState(Maybe.Nothing());
26+ const [downloaded, setDownloaded] = useState(false);
27+
28+ const pointAsString = derivedPoint.matchWith({
29+ Nothing: () => '',
30+ Just: p => p.value.toFixed(),
31+ });
32+
33+ const download = useCallback(() => {
34+ downloadWallet(paper.getOrElse([]));
35+ setDownloaded(true);
36+ }, [paper, setDownloaded]);
37+
38+ // sync paper value to activation state
39+ useEffect(
40+ () =>
41+ setGenerated(
42+ paper.matchWith({
43+ Nothing: () => false,
44+ Just: () => true,
45+ })
46+ ),
47+ [paper, setGenerated]
48+ );
49+
50+ const goToVerify = useCallback(() => push(names.VERIFY), [push, names]);
51+
52+ return (
53+ <Grid gap={4} className={className}>
54+ <Grid.Item full as={Steps} num={1} total={3} />
55+ <Grid.Item full as={H4}>
56+ Passport
57+ </Grid.Item>
58+ <Grid.Item full>
59+ <P>
60+ After you’ve downloaded your passport, back up the ticket manually or
61+ store on a trusted device.
62+ </P>
63+ <P>
64+ What is digital identity? A passport is your digital identity. You
65+ will use your passport to access your true computer, send payments,
66+ and administer your identity. So naturally, you must keep this secure.
67+ </P>
68+ </Grid.Item>
69+ <Grid.Item
70+ full
71+ as={!downloaded ? DownloadButton : ForwardButton}
72+ disabled={!generated}
73+ onClick={!downloaded ? download : goToVerify}
74+ success={downloaded}
75+ solid>
76+ {!downloaded ? 'Download Passport' : 'Continue'}
77+ </Grid.Item>
78+ <PaperCollateralRenderer
79+ className="super-hidden"
80+ wallet={{ [pointAsString]: wallet }}
81+ callback={data => {
82+ console.log(data);
83+ setPaper(Maybe.Just(data));
84+ }}
85+ mode="REGISTRATION"
86+ />
87+ </Grid>
88+ );
89+}
src/views/Activate/PassportTransfer.jsnew file+64
@@ -0,0 +1,64 @@
1+import React, { useState, useEffect, useCallback } from 'react';
2+import Maybe from 'folktale/maybe';
3+import { Grid, H4, P, Text } from 'indigo-react';
4+import PaperCollateralRenderer from 'PaperCollateralRenderer';
5+
6+import * as need from 'lib/need';
7+import { useLocalRouter } from 'lib/LocalRouter';
8+
9+import { DownloadButton, ForwardButton } from 'components/Buttons';
10+import Steps from 'components/Steps';
11+
12+import { useActivateFlow } from './ActivateFlow';
13+import useLifecycle from 'lib/useLifecycle';
14+import WarningBox from 'components/WarningBox';
15+import LoadingBar from 'components/LoadingBar';
16+
17+const STATUS = {
18+ CLAIMING: 'CLAIMING',
19+ NETWORKING: 'NETWORKING',
20+ REDIRECTING: 'REDIRECTING',
21+};
22+
23+export default function PassportTransfer({ className }) {
24+ const { derivedWallet, derivedPoint, inviteWallet } = useActivateFlow();
25+ const [status, setStatus] = useState(STATUS.CLAIMING);
26+ const isClaiming = status === STATUS.CLAIMING;
27+
28+ const titleText = (() => {
29+ switch (status) {
30+ case STATUS.CLAIMING:
31+ return 'Claiming Point';
32+ case STATUS.NETWORKING:
33+ return 'Setting Networking Keys';
34+ case STATUS.REDIRECTING:
35+ return 'Transporting to Bridge';
36+ default:
37+ break;
38+ }
39+ })();
40+
41+ useLifecycle(() => {
42+ // on mount, start transactions
43+ });
44+
45+ return (
46+ <Grid gap={4} className={className}>
47+ <Grid.Item full as={Steps} num={2} total={3} />
48+ <Grid.Item full as={H4}>
49+ {titleText}
50+ </Grid.Item>
51+ <Grid.Item full as={LoadingBar} progress={0.5} />
52+ <Grid.Item full as={Text} className="green4">
53+ This process can take up to 5 minutes to complete. Don't leave this page
54+ until the process is complete.
55+ </Grid.Item>
56+
57+ {isClaiming && (
58+ <Grid.Item full as={WarningBox}>
59+ Never give your Master Ticket to anyone
60+ </Grid.Item>
61+ )}
62+ </Grid>
63+ );
64+}
src/views/Activate/PassportVerify.jsnew file+57
@@ -0,0 +1,57 @@
1+import React, { useCallback, useMemo } from 'react';
2+import { Grid, H4, P, Input } from 'indigo-react';
3+
4+import * as need from 'lib/need';
5+import { useLocalRouter } from 'lib/LocalRouter';
6+
7+import { ForwardButton } from 'components/Buttons';
8+import Steps from 'components/Steps';
9+import { useTicketInput } from 'components/Inputs';
10+
11+import { useActivateFlow } from './ActivateFlow';
12+import { validateExactly } from 'lib/validators';
13+
14+export default function PassportVerify({ className }) {
15+ const { push, names } = useLocalRouter();
16+ const { derivedWallet } = useActivateFlow();
17+ const goToTransfer = useCallback(() => push(names.TRANSFER), [push, names]);
18+
19+ const { ticket } = need.wallet(derivedWallet);
20+ const validators = useMemo(
21+ () => [validateExactly(ticket, 'Does not match expected master ticket.')],
22+ [ticket]
23+ );
24+ const ticketInput = useTicketInput({
25+ name: 'ticket',
26+ label: 'Master Ticket',
27+ autoFocus: true,
28+ validators,
29+ });
30+ console.log(ticket, ticketInput.data);
31+ const { pass } = ticketInput;
32+
33+ return (
34+ <Grid gap={4} className={className}>
35+ <Grid.Item full as={Steps} num={2} total={3} />
36+ <Grid.Item full as={H4}>
37+ Verify Passport
38+ </Grid.Item>
39+ <Grid.Item full>
40+ <P>
41+ After you download your passport, verify your custody. Your passport
42+ should be a folder of image files. One of them is your Master Ticket.
43+ Open it and enter the 4 word phrase below (with hyphens).
44+ </P>
45+ </Grid.Item>
46+ <Grid.Item full as={Input} {...ticketInput} />
47+ <Grid.Item
48+ full
49+ as={ForwardButton}
50+ disabled={!pass}
51+ onClick={goToTransfer}
52+ solid>
53+ Verify
54+ </Grid.Item>
55+ </Grid>
56+ );
57+}
src/views/Activate/useActivateFlowState.js+3
@@ -5,6 +5,7 @@ export default function useActivateFlowState() {
55 const [derivedWallet, setDerivedWallet] = useState(Maybe.Nothing());
66 const [inviteWallet, setInviteWallet] = useState(Maybe.Nothing());
77 const [derivedPoint, setDerivedPoint] = useState(Maybe.Nothing());
8+ const [generated, setGenerated] = useState(false);
89
910 return {
1011 derivedWallet,
@@ -13,5 +14,7 @@ export default function useActivateFlowState() {
1314 setInviteWallet,
1415 derivedPoint,
1516 setDerivedPoint,
17+ generated,
18+ setGenerated,
1619 };
1720}
src/views/Invite.js+1−1
@@ -71,7 +71,7 @@ export default function Invite() {
7171 <Grid.Item as={H5} className="mv4" full>
7272 Invite
7373 </Grid.Item>
74− <Grid.Item as={Text} full>
74+ <Grid.Item as={Text} className="f5" full>
7575 You currently have{' '}
7676 <Highlighted>{availableInvitesText} invitations</Highlighted> left.
7777 <br />
src/views/Invite/InviteEmail.js+1−1
@@ -411,7 +411,7 @@ export default function InviteEmail() {
411411
412412 {isDone && (
413413 <>
414− <Grid.Item as={Text} full>
414+ <Grid.Item as={Text} className="f5" full>
415415 <Highlighted>{pluralize(inputs.length, 'invite')}</Highlighted>{' '}
416416 {pluralize(inputs.length, 'has', 'have')} been successfully sent
417417 </Grid.Item>