on master help sign in
feat: implement disclaimer, passport download & verify
4b25329Matt Condon (shrugs)
src/components/Inputs.js+11−8
@@ -1,4 +1,4 @@
1−import React, { useState } from 'react';
1+import React, { useState, useMemo } from 'react';
2import * as bip39 from 'bip39';
2import * as bip39 from 'bip39';
3
3
4import InputSigil from 'components/InputSigil';
4import InputSigil from 'components/InputSigil';
@@ -13,6 +13,7 @@ import {
13} from 'lib/validators';
13} from 'lib/validators';
14import { prependSig } from 'lib/transformers';
14import { prependSig } from 'lib/transformers';
15import useForm from 'indigo-react/lib/useForm';
15import useForm from 'indigo-react/lib/useForm';
16+import { AccessoryIcon } from 'indigo-react';
16
17
17// const RequiredInput = advancedInput({
18// const RequiredInput = advancedInput({
18// WrappedComponent: Input,
19// WrappedComponent: Input,
@@ -87,26 +88,29 @@ export function useHdPathInput(props) {
87const kTicketValidators = [validateTicket, validateNotEmpty];
88const kTicketValidators = [validateTicket, validateNotEmpty];
88const kTicketTransformers = [prependSig];
89const kTicketTransformers = [prependSig];
89//TODO needs to be fancier, displaying sig and dashes instead of •ing all
90//TODO needs to be fancier, displaying sig and dashes instead of •ing all
90−export function useTicketInput({ initialValue = '~', ...rest }) {
91+export function useTicketInput({ validators = [], ...rest }) {
91− return firstOf(
92+ const input = firstOf(
92 useForm([
93 useForm([
93 {
94 {
94 type: 'password',
95 type: 'password',
95 label: 'Ticket',
96 label: 'Ticket',
96− placeholder: '~master-ticket',
97+ placeholder: '~ragtyd-modwen-bostec-hinsep',
97− validators: kTicketValidators,
98+ validators: useMemo(() => [...validators, ...kTicketValidators], [
99+ validators,
100+ ]),
98 transformers: kTicketTransformers,
101 transformers: kTicketTransformers,
99 mono: true,
102 mono: true,
100− initialValue,
101 ...rest,
103 ...rest,
102 },
104 },
103 ])
105 ])
104 );
106 );
107+
108+ return { ...input, accessory: input.pass ? <AccessoryIcon.Success /> : null };
105}
109}
106
110
107const kPointValidators = [validatePoint, validateNotEmpty];
111const kPointValidators = [validatePoint, validateNotEmpty];
108const kPointTransformers = [prependSig];
112const kPointTransformers = [prependSig];
109−export function usePointInput({ initialValue = '~', ...rest }) {
113+export function usePointInput(rest) {
110 const [lastValidPoint, setLastValidPoint] = useState('');
114 const [lastValidPoint, setLastValidPoint] = useState('');
111 const input = firstOf(
115 const input = firstOf(
112 useForm([
116 useForm([
@@ -117,7 +121,6 @@ export function usePointInput({ initialValue = '~', ...rest }) {
117 validators: kPointValidators,
121 validators: kPointValidators,
118 transformers: kPointTransformers,
122 transformers: kPointTransformers,
119 mono: true,
123 mono: true,
120− initialValue,
121 onValue: setLastValidPoint,
124 onValue: setLastValidPoint,
122 ...rest,
125 ...rest,
123 },
126 },
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 =>
16const kDateA = buildDate(kLoadingCharacter);
16const kDateA = buildDate(kLoadingCharacter);
17const kDateB = buildDate(kInterstitialCharacter);
17const kDateB = buildDate(kInterstitialCharacter);
18
18
19−const kTicketCharacterLength = 40;
19+const kTicketCharacterLength = 30;
20−const kPendingTicket = times(
21− kTicketCharacterLength,
20+const kTicket = times(kTicketCharacterLength, i =>
22− () => kLoadingCharacter
21+ i % 2 === 0 ? kLoadingCharacter : kInterstitialCharacter
23).join('');
22).join('');
24−const kValidTicket =
23+const kPendingTicket = kTicket;
25− times(kTicketCharacterLength, () => kLoadingCharacter).join('') + '◆';
24+const kValidTicket = `${kTicket} ◆`;
26
25
27const kVisibleAddressCharacters = 20;
26const kVisibleAddressCharacters = 20;
28const kEmptyHalfAddress = times(kVisibleAddressCharacters, () => '<').join('');
27const kEmptyHalfAddress = times(kVisibleAddressCharacters, () => '<').join('');
29−const bufferAddressCharacters = times(40, () => '<').join('');
28+const kBufferAddressCharacters = times(40, () => '<').join('');
30
29
31/**
30/**
32 * point is Maybe<number>
31 * point is Maybe<number>
33− * ticket is boolean
32+ * ticket is null | boolean
34 * address is null | Maybe<string>
33 * address is null | Maybe<string>
35 */
34 */
36export default function Passport({
35export default function Passport({
@@ -63,7 +62,7 @@ export default function Passport({
63 `${address.matchWith({
62 `${address.matchWith({
64 Nothing: () => kEmptyHalfAddress,
63 Nothing: () => kEmptyHalfAddress,
65 Just: p => p.value.slice(0, kVisibleAddressCharacters),
64 Just: p => p.value.slice(0, kVisibleAddressCharacters),
66− })}${bufferAddressCharacters}`;
65+ })}${kBufferAddressCharacters}`;
67
66
68 return (
67 return (
69 <Grid gap={4} className={cn('bg-black r8 p7', className)}>
68 <Grid gap={4} className={cn('bg-black r8 p7', className)}>
@@ -73,27 +72,27 @@ export default function Passport({
73 <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
72 <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
74 <Flex.Item className="mono white f5">{visualName}</Flex.Item>
73 <Flex.Item className="mono white f5">{visualName}</Flex.Item>
75 <Flex.Item as={Flex} col>
74 <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">
77 Birth Time
76 Birth Time
78 </Flex.Item>
77 </Flex.Item>
79− <Flex.Item as={Text} className="mono gray4">
78+ <Flex.Item as={Text} className="mono f6 f5-ns gray4 ">
80 {visualBirthday}
79 {visualBirthday}
81 </Flex.Item>
80 </Flex.Item>
82 </Flex.Item>
81 </Flex.Item>
83 </Grid.Item>
82 </Grid.Item>
84 {!mini && showTicket && (
83 {!mini && showTicket && (
85 <Grid.Item full className="mv3" as={Flex} col>
84 <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">
87 Master Ticket
86 Master Ticket
88 </Flex.Item>
87 </Flex.Item>
89 <Flex.Item
88 <Flex.Item
90 as={Text}
89 as={Text}
91− className={cn('mono gray4', {
90+ className={cn('mono f6 f5-ns gray4 wrap', {
92 green3: showValidTicket,
91 green3: showValidTicket,
93 })}>
92 })}>
94 {visibleTicket}
93 {visibleTicket}
95 </Flex.Item>
94 </Flex.Item>
96− <Flex.Item as={Text} className="mono gray4">
95+ <Flex.Item as={Text} className="mono f6 f5-ns gray4 wrap">
97 {visibleAddress}
96 {visibleAddress}
98 </Flex.Item>
97 </Flex.Item>
99 </Grid.Item>
98 </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 @@
1import React from 'react';
1import React from 'react';
2import cn from 'classnames';
2import cn from 'classnames';
3
3
4−function Grid({ as: As = 'div', gap = 0, className, ...rest }) {
4+function Grid({ as: As = 'div', gap = 0, align, justify, className, ...rest }) {
5 return (
5 return (
6+ <As
6− <As className={cn('grid12', gap && `gap${gap}`, className)} {...rest} />
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+ />
7 );
18 );
8}
19}
9
20
src/indigo-react/components/Typography.js+1−1
@@ -17,6 +17,6 @@ export const P = typeWith('p');
17export const B = typeWith('b');
17export const B = typeWith('b');
18
18
19export const Breadcrumb = typeWith('span', 'f6');
19export const Breadcrumb = typeWith('span', 'f6');
20−export const Text = typeWith('span', 'f5');
20+export const Text = typeWith('span');
21export const HelpText = typeWith('span', 'f6 gray4');
21export const HelpText = typeWith('span', 'f6 gray4');
22export const ErrorText = typeWith('span', 'f6 red3');
22export const ErrorText = typeWith('span', 'f6 red3');
src/indigo-react/indigo.css+13−26
@@ -13,7 +13,7 @@
13 * git+https://github.com/urbit/Indigo.git
13 * git+https://github.com/urbit/Indigo.git
14 * Bugs: https://github.com/urbit/Indigo/issues
14 * Bugs: https://github.com/urbit/Indigo/issues
15 * License: MIT
15 * 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)
17 *******************************************************************************/
17 *******************************************************************************/
18/**********************
18/**********************
19Color variables
19Color variables
@@ -734,13 +734,18 @@ caption,
734.pre {
734.pre {
735 white-space: pre; }
735 white-space: pre; }
736
736
737+.wrap {
738+ word-break: break-all; }
739+
737@media only screen and (min-width: 550px) {
740@media only screen and (min-width: 550px) {
738 .ws-normal-ns {
741 .ws-normal-ns {
739 white-space: normal; }
742 white-space: normal; }
740 .nowrap-ns {
743 .nowrap-ns {
741 white-space: nowrap; }
744 white-space: nowrap; }
742 .pre-ns {
745 .pre-ns {
743− white-space: pre; } }
746+ white-space: pre; }
747+ .wrap-ns {
748+ word-break: break-all; } }
744
749
745@media only screen and (min-width: 750px) and (max-width: 960px) {
750@media only screen and (min-width: 750px) and (max-width: 960px) {
746 .ws-normal-md {
751 .ws-normal-md {
@@ -748,7 +753,9 @@ caption,
748 .nowrap-md {
753 .nowrap-md {
749 white-space: nowrap; }
754 white-space: nowrap; }
750 .pre-md {
755 .pre-md {
751− white-space: pre; } }
756+ white-space: pre; }
757+ .wrap-md {
758+ word-break: break-all; } }
752
759
753@media only screen and (min-width: 960px) {
760@media only screen and (min-width: 960px) {
754 .ws-normal-lg {
761 .ws-normal-lg {
@@ -756,7 +763,9 @@ caption,
756 .nowrap-lg {
763 .nowrap-lg {
757 white-space: nowrap; }
764 white-space: nowrap; }
758 .pre-lg {
765 .pre-lg {
759− white-space: pre; } }
766+ white-space: pre; }
767+ .wrap-lg {
768+ word-break: break-all; } }
760
769
761/*******************************************************************************
770/*******************************************************************************
762 Border Radius
771 Border Radius
@@ -1531,43 +1540,33 @@ caption,
1531 flex-wrap: wrap; }
1540 flex-wrap: wrap; }
1532
1541
1533.align-start {
1542.align-start {
1534− display: flex;
1535 align-items: flex-start; }
1543 align-items: flex-start; }
1536
1544
1537.align-end {
1545.align-end {
1538− display: flex;
1539 align-items: flex-end; }
1546 align-items: flex-end; }
1540
1547
1541.align-center {
1548.align-center {
1542− display: flex;
1543 align-items: center; }
1549 align-items: center; }
1544
1550
1545.align-baseline {
1551.align-baseline {
1546− display: flex;
1547 align-items: baseline; }
1552 align-items: baseline; }
1548
1553
1549.align-stretch {
1554.align-stretch {
1550− display: flex;
1551 align-items: stretch; }
1555 align-items: stretch; }
1552
1556
1553.justify-start {
1557.justify-start {
1554− display: flex;
1555 justify-content: flex-start; }
1558 justify-content: flex-start; }
1556
1559
1557.justify-end {
1560.justify-end {
1558− display: flex;
1559 justify-content: flex-end; }
1561 justify-content: flex-end; }
1560
1562
1561.justify-center {
1563.justify-center {
1562− display: flex;
1563 justify-content: center; }
1564 justify-content: center; }
1564
1565
1565.justify-between {
1566.justify-between {
1566− display: flex;
1567 justify-content: space-between; }
1567 justify-content: space-between; }
1568
1568
1569.justify-around {
1569.justify-around {
1570− display: flex;
1571 justify-content: space-around; }
1570 justify-content: space-around; }
1572
1571
1573.flex-center {
1572.flex-center {
@@ -1627,16 +1626,12 @@ caption,
1627 .flex-ns {
1626 .flex-ns {
1628 display: flex; }
1627 display: flex; }
1629 .flex-row-ns {
1628 .flex-row-ns {
1630− display: flex;
1631 flex-direction: row; }
1629 flex-direction: row; }
1632 .flex-row-r-ns {
1630 .flex-row-r-ns {
1633− display: flex;
1634 flex-direction: row-reverse; }
1631 flex-direction: row-reverse; }
1635 .flex-col-ns {
1632 .flex-col-ns {
1636− display: flex;
1637 flex-direction: column; }
1633 flex-direction: column; }
1638 .flex-col-r-ns {
1634 .flex-col-r-ns {
1639− display: flex;
1640 flex-direction: column-reverse; }
1635 flex-direction: column-reverse; }
1641 .flex-wrap-ns {
1636 .flex-wrap-ns {
1642 flex-wrap: wrap; }
1637 flex-wrap: wrap; }
@@ -1700,16 +1695,12 @@ caption,
1700 .flex-md {
1695 .flex-md {
1701 display: flex; }
1696 display: flex; }
1702 .flex-row-md {
1697 .flex-row-md {
1703− display: flex;
1704 flex-direction: row; }
1698 flex-direction: row; }
1705 .flex-row-r-md {
1699 .flex-row-r-md {
1706− display: flex;
1707 flex-direction: row-reverse; }
1700 flex-direction: row-reverse; }
1708 .flex-col-md {
1701 .flex-col-md {
1709− display: flex;
1710 flex-direction: column; }
1702 flex-direction: column; }
1711 .flex-col-r-md {
1703 .flex-col-r-md {
1712− display: flex;
1713 flex-direction: column-reverse; }
1704 flex-direction: column-reverse; }
1714 .flex-wrap-md {
1705 .flex-wrap-md {
1715 flex-wrap: wrap; }
1706 flex-wrap: wrap; }
@@ -1773,16 +1764,12 @@ caption,
1773 .flex-lg {
1764 .flex-lg {
1774 display: flex; }
1765 display: flex; }
1775 .flex-row-lg {
1766 .flex-row-lg {
1776− display: flex;
1777 flex-direction: row; }
1767 flex-direction: row; }
1778 .flex-row-r-lg {
1768 .flex-row-r-lg {
1779− display: flex;
1780 flex-direction: row-reverse; }
1769 flex-direction: row-reverse; }
1781 .flex-col-lg {
1770 .flex-col-lg {
1782− display: flex;
1783 flex-direction: column; }
1771 flex-direction: column; }
1784 .flex-col-r-lg {
1772 .flex-col-r-lg {
1785− display: flex;
1786 flex-direction: column-reverse; }
1773 flex-direction: column-reverse; }
1787 .flex-wrap-lg {
1774 .flex-wrap-lg {
1788 flex-wrap: wrap; }
1775 flex-wrap: wrap; }
src/indigo-react/lib/useForm.js+7−6
@@ -29,11 +29,7 @@ export default function useForm(configs = []) {
29
29
30 // track values
30 // track values
31 const [values, _setValues] = useState(() =>
31 const [values, _setValues] = useState(() =>
32− defaultsFor(configs, config => config.initialValue)
32+ defaultsFor(configs, config => config.initialValue || '')
33− );
34− const setValue = useCallback(
35− (name, value) => _setValues(values => ({ ...values, [name]: value })),
36− [_setValues]
37 );
33 );
38 // ^ NB(shrugs): because we're not syncing the initialValue of _additional_
34 // ^ NB(shrugs): because we're not syncing the initialValue of _additional_
39 // configs that are added, we won't be able to set an initialValue for a
35 // configs that are added, we won't be able to set an initialValue for a
@@ -42,6 +38,11 @@ export default function useForm(configs = []) {
42 // but for dynamically added input configs, we won't get validation, etc
38 // but for dynamically added input configs, we won't get validation, etc
43 // until there's a traditional state update.
39 // until there's a traditional state update.
44
40
41+ const setValue = useCallback(
42+ (name, value) => _setValues(values => ({ ...values, [name]: value })),
43+ [_setValues]
44+ );
45+
45 // track focused states
46 // track focused states
46 const [focused, setFocused] = useSetState(() =>
47 const [focused, setFocused] = useSetState(() =>
47 defaultsFor(configs, config => config.autoFocus && !config.disabled)
48 defaultsFor(configs, config => config.autoFocus && !config.disabled)
@@ -165,7 +166,7 @@ export default function useForm(configs = []) {
165 ...rest,
166 ...rest,
166 // dom properties below:
167 // dom properties below:
167 bind: {
168 bind: {
168− value: values[name] || initialValue,
169+ value: values[name] || initialValue || '',
169 checked: !!values[name],
170 checked: !!values[name],
170 onChange: onChange(name),
171 onChange: onChange(name),
171 onFocus: onFocus(name),
172 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({
55 [setRoutes]
55 [setRoutes]
56 );
56 );
57 const peek = useCallback(() => last(routes), [routes]);
57 const peek = useCallback(() => last(routes), [routes]);
58+ const replaceWith = useCallback(routes => setRoutes(routes), [setRoutes]);
58 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
59 const includes = useCallback(key => _includes(routes.map(r => r.key), key), [
59 routes,
60 routes,
60 ]);
61 ]);
@@ -90,6 +91,7 @@ export default function useRouter({
90 push,
91 push,
91 popAndPush,
92 popAndPush,
92 pop,
93 pop,
94+ replaceWith,
93 peek,
95 peek,
94 size,
96 size,
95 includes,
97 includes,
src/lib/validators.js+7
@@ -185,3 +185,10 @@ export const validateInt = m =>
185 },
185 },
186 error: 'This is not a valid number',
186 error: 'This is not a valid number',
187 });
187 });
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';
8import { PointCursorProvider } from './pointCursor';
8import { PointCursorProvider } from './pointCursor';
9import { PointCacheProvider } from './pointCache';
9import { PointCacheProvider } from './pointCache';
10import { TxnCursorProvider } from './txnCursor';
10import { TxnCursorProvider } from './txnCursor';
11+import { MediaQueryProvider } from 'lib/MediaQuery';
11
12
12// nest all of the providers within each other to avoid hella depth
13// nest all of the providers within each other to avoid hella depth
13export default nest([
14export default nest([
@@ -19,4 +20,5 @@ export default nest([
19 PointCursorProvider,
20 PointCursorProvider,
20 PointCacheProvider,
21 PointCacheProvider,
21 TxnCursorProvider,
22 TxnCursorProvider,
23+ MediaQueryProvider,
22]);
24]);
src/views/Activate/ActivateCode.js+8−18
@@ -68,32 +68,22 @@ export default function ActivateCode() {
68 );
68 );
69
69
70 //TODO isn't all this accessible in the ownership object?
70 //TODO isn't all this accessible in the ownership object?
71− const inviteWallet = walletFromMnemonic(
71+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
72− seed,
72+ const _inviteWallet = need.wallet(inviteWallet);
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− });
81
73
82 const incoming = await azimuth.azimuth.getTransferringFor(
74 const incoming = await azimuth.azimuth.getTransferringFor(
83 _contracts,
75 _contracts,
84− inviteWallet.address
76+ _inviteWallet.address
85 );
77 );
86
78
87 let realPoint = Maybe.Nothing();
79 let realPoint = Maybe.Nothing();
88− let realWallet = Maybe.Nothing();
80+ let wallet = Maybe.Nothing();
89
81
90 if (incoming.length > 0) {
82 if (incoming.length > 0) {
91− let pointNum = parseInt(incoming[0], 10);
83+ const pointNum = parseInt(incoming[0], 10);
92 realPoint = Maybe.Just(pointNum);
84 realPoint = Maybe.Just(pointNum);
85+ wallet = Maybe.Just(await generateWallet(pointNum));
93
86
94− const genWallet = await generateWallet(pointNum);
95−
96− realWallet = Maybe.Just(genWallet);
97 if (incoming.length > 1) {
87 if (incoming.length > 1) {
98 setGeneralError(
88 setGeneralError(
99 'This invite code has multiple points available.\n' +
89 'This invite code has multiple points available.\n' +
@@ -108,8 +98,8 @@ export default function ActivateCode() {
108 }
98 }
109
99
110 setDerivedPoint(realPoint);
100 setDerivedPoint(realPoint);
111− setDerivedWallet(realWallet);
101+ setDerivedWallet(wallet);
112− setInviteWallet(Maybe.Just(inviteWallet));
102+ setInviteWallet(inviteWallet);
113 setDeriving(false);
103 setDeriving(false);
114 })();
104 })();
115 } else {
105 } else {
src/views/Activate/ActivateDisclaimer.js+3−4
@@ -5,6 +5,7 @@ import View from 'components/View';
5import { useCheckboxInput } from 'components/Inputs';
5import { useCheckboxInput } from 'components/Inputs';
6import { ForwardButton } from 'components/Buttons';
6import { ForwardButton } from 'components/Buttons';
7import { useLocalRouter } from 'lib/LocalRouter';
7import { useLocalRouter } from 'lib/LocalRouter';
8+import WarningBox from 'components/WarningBox';
8
9
9export default function ActivateDisclaimer() {
10export default function ActivateDisclaimer() {
10 const { push, names } = useLocalRouter();
11 const { push, names } = useLocalRouter();
@@ -63,10 +64,8 @@ export default function ActivateDisclaimer() {
63 problems that may arise in connection with your wallet.
64 problems that may arise in connection with your wallet.
64 </P>
65 </P>
65 </Grid.Item>
66 </Grid.Item>
66− <Grid.Item as={Flex} className="bg-red1 pv3 ph4" align="center" full>
67+ <Grid.Item full as={WarningBox}>
67− <Text className="red3 fw-bold">
68− Warning: We Can't Restore of Reset Your Master Ticket
68+ Warning: We Can't Restore of Reset Your Master Ticket
69− </Text>
70 </Grid.Item>
69 </Grid.Item>
71 <Grid.Item as={CheckboxInput} {...checkboxInput} full />
70 <Grid.Item as={CheckboxInput} {...checkboxInput} full />
72 <Grid.Item
71 <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';
2import Maybe from 'folktale/maybe';
3import Maybe from 'folktale/maybe';
3−import { Grid, H4, P } from 'indigo-react';
4+import { Grid } from 'indigo-react';
4−import PaperCollateralRenderer from 'PaperCollateralRenderer';
5
5
6−import * as need from 'lib/need';
7−import { useLocalRouter } from 'lib/LocalRouter';
6+import { LocalRouterProvider } from 'lib/LocalRouter';
8
7
9import View from 'components/View';
8import View from 'components/View';
10−import { ForwardButton, DownloadButton } from 'components/Buttons';
11import Passport from 'components/Passport';
9import Passport from 'components/Passport';
12
10
13import { useActivateFlow } from './ActivateFlow';
11import { useActivateFlow } from './ActivateFlow';
14−import { downloadWallet } from 'lib/invite';
12+import useBreakpoints from 'lib/useBreakpoints';
13+import useRouter from 'lib/useRouter';
15
14
16−export default function ActivatePassport() {
15+import PassportDownload from './PassportDownload';
17− const { push, names } = useLocalRouter();
16+import PassportVerify from './PassportVerify';
18− const { derivedPoint, derivedWallet } = useActivateFlow();
17+import PassportTransfer from './PassportTransfer';
19− // const point = need.point(derivedPoint);
20− const wallet = need.wallet(derivedWallet);
21
18
22− const [paper, setPaper] = useState(Maybe.Nothing());
19+const kPassportNames = {
20+ DOWNLOAD: 'DOWNLOAD',
21+ VERIFY: 'VERIFY',
22+ TRANSFER: 'TRANSFER',
23+};
23
24
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+};
25
30
31+export default function ActivatePassport() {
26− const pointAsString = derivedPoint.matchWith({
32+ const { derivedPoint, generated } = useActivateFlow();
27− Nothing: () => '',
28− Just: p => p.value.toFixed(),
29− });
30
33
31− const generated = paper.matchWith({
34+ const { Route, ...router } = useRouter({
32− Nothing: () => false,
35+ names: kPassportNames,
33− Just: () => true,
36+ views: kPassportViews,
37+ initialRoutes: [{ key: kPassportNames.DOWNLOAD }],
34 });
38 });
35
39
36− const download = useCallback(() => downloadWallet(paper.getOrElse([])), [
40+ const fullView = useBreakpoints([false, false, true]);
37− paper,
41+ const gap = useBreakpoints([4, 4, 7]);
38− ]);
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]);
39
46
40 return (
47 return (
41− <View.Full>
48+ <LocalRouterProvider value={router}>
49+ <View full={fullView}>
42− <Grid gap={10} className="mt8 mb10" align="center">
50+ <Grid gap={gap} className="mt8 mb10">
43− <Grid.Item half={1} alignSelf="center">
51+ <Grid.Item half={leftHalf} full={full}>
52+ <Passport
53+ className={cn({ [`mt${marginTop}`]: marginTop })}
44− <Passport point={derivedPoint} ticket={generated} />
54+ point={derivedPoint}
45− </Grid.Item>
55+ ticket={generated}
46− <Grid.Item half={2} as={Grid} gap={5} alignSelf="center">
56+ address={Maybe.Nothing()}
47− <Grid.Item full>Step 1 of 3</Grid.Item>
57+ />
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
68 </Grid.Item>
58 </Grid.Item>
69− </Grid.Item>
59+ <Grid.Item half={rightHalf} full={full} as={Route} />
70− </Grid>
60+ </Grid>
71− <PaperCollateralRenderer
61+ </View>
72− className="super-hidden"
62+ </LocalRouterProvider>
73− wallet={{ [pointAsString]: wallet }}
74− callback={data => {
75− console.log(data);
76− setPaper(Maybe.Just(data));
77− }}
78− mode="REGISTRATION"
79− />
80− </View.Full>
81 );
63 );
82}
64}
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() {
5 const [derivedWallet, setDerivedWallet] = useState(Maybe.Nothing());
5 const [derivedWallet, setDerivedWallet] = useState(Maybe.Nothing());
6 const [inviteWallet, setInviteWallet] = useState(Maybe.Nothing());
6 const [inviteWallet, setInviteWallet] = useState(Maybe.Nothing());
7 const [derivedPoint, setDerivedPoint] = useState(Maybe.Nothing());
7 const [derivedPoint, setDerivedPoint] = useState(Maybe.Nothing());
8+ const [generated, setGenerated] = useState(false);
8
9
9 return {
10 return {
10 derivedWallet,
11 derivedWallet,
@@ -13,5 +14,7 @@ export default function useActivateFlowState() {
13 setInviteWallet,
14 setInviteWallet,
14 derivedPoint,
15 derivedPoint,
15 setDerivedPoint,
16 setDerivedPoint,
17+ generated,
18+ setGenerated,
16 };
19 };
17}
20}
src/views/Invite.js+1−1
@@ -71,7 +71,7 @@ export default function Invite() {
71 <Grid.Item as={H5} className="mv4" full>
71 <Grid.Item as={H5} className="mv4" full>
72 Invite
72 Invite
73 </Grid.Item>
73 </Grid.Item>
74− <Grid.Item as={Text} full>
74+ <Grid.Item as={Text} className="f5" full>
75 You currently have{' '}
75 You currently have{' '}
76 <Highlighted>{availableInvitesText} invitations</Highlighted> left.
76 <Highlighted>{availableInvitesText} invitations</Highlighted> left.
77 <br />
77 <br />
src/views/Invite/InviteEmail.js+1−1
@@ -411,7 +411,7 @@ export default function InviteEmail() {
411
411
412 {isDone && (
412 {isDone && (
413 <>
413 <>
414− <Grid.Item as={Text} full>
414+ <Grid.Item as={Text} className="f5" full>
415 <Highlighted>{pluralize(inputs.length, 'invite')}</Highlighted>{' '}
415 <Highlighted>{pluralize(inputs.length, 'invite')}</Highlighted>{' '}
416 {pluralize(inputs.length, 'has', 'have')} been successfully sent
416 {pluralize(inputs.length, 'has', 'have')} been successfully sent
417 </Grid.Item>
417 </Grid.Item>