on master help sign in
checkpoint: implementing disclaimer, passport screens
commit1c0703e3d9441c978091d05f31a47aef30c1a55b authorMatt Condon (shrugs) <mattgcondon@gmail.com> dateThu 27 Jun 2019, 14:46 (7 years ago) parentfb6d07a
41 files changed+678−131
src/components/Blinky.js+4−3 src/components/Buttons.js+9 src/components/FooterButton.js+2−2 src/components/Inputs.js+11 src/components/Passport.js+57−6 src/indigo-react/components/AccessoryIcon.js+1−1 src/indigo-react/components/Button.js+1−1 src/indigo-react/components/CheckboxInput.js+82 src/indigo-react/components/Grid.js+7−2 src/indigo-react/components/Input.js+10−10 src/indigo-react/components/Typography.js+1 src/indigo-react/index.js+1 src/indigo-react/indigo.css+27−12 src/indigo-react/lib/useForm.js+14−2 src/lib/constants.js+3 src/lib/keys.js+1−1 src/lib/need.js+27−12 src/lib/useRouter.js+1−5 src/lib/validators.js+1−1 src/lib/walletgen.js+5−2 src/store/lib/useBirthdaysStore.js+6 src/style/indigo-proposals.scss+4 src/views/AcceptTransfer.js+2−2 src/views/Activate.js+29−51 src/views/Activate/ActivateCode.js+163 src/views/Activate/ActivateDisclaimer.js+83 src/views/Activate/ActivateFlow.js+9 src/views/Activate/ActivatePassport.js+82 src/views/Activate/useActivateFlowState.js+17 src/views/CancelTransfer.js+2−2 src/views/GenKeyfile.js+1−1 src/views/Invite.js+1−1 src/views/Invite/InviteEmail.js+3−3 src/views/Invite/InviteUrl.js+1−1 src/views/InvitesManage.js+1−1 src/views/IssueChild.js+1−1 src/views/Point.js+1−1 src/views/Point/Actions.js+1−1 src/views/SetKeys.js+2−2 src/views/SetProxy.js+2−2 src/views/Transfer.js+2−2
src/components/Blinky.js+4−3
@@ -9,20 +9,21 @@ const BLINK_AFTER_MS = 2500; // ms
99
1010export const matchBlinky = obj =>
1111 obj.matchWith({
12− Nothing: () => <Blinky />,
12+ Nothing: () => <Blinky delayed />,
1313 Just: p => p.value,
1414 });
1515
1616export default function Blinky({
1717 a = kLoadingCharacter,
1818 b = kInterstitialCharacter,
19+ delayed = false,
1920}) {
2021 const [value, setValue] = useState(true);
2122 const now = useRef(new Date());
2223
2324 useInterval(() => {
24− // only start blinking if we've elapsed enough time
25− if (new Date() - now.current > BLINK_AFTER_MS) {
25+ // only start blinking if we've elapsed enough time and want to delay
26+ if (!delayed || new Date() - now.current > BLINK_AFTER_MS) {
2627 setValue(val => !val);
2728 }
2829 }, 1000);
src/components/Buttons.js+9
@@ -4,3 +4,12 @@ import { Button } from 'indigo-react';
44// TODO: use svg icons
55export const ForwardButton = props => <Button accessory="→" {...props} />;
66export const DownloadButton = props => <Button accessory="⬇" {...props} />;
7+export const OutButton = props => (
8+ <Button as="a" target="_blank" accessory="↗" {...props} />
9+);
10+
11+export const OfflineButton = props => (
12+ <OutButton {...props} href="https://github.com/urbit/bridge/releases">
13+ Offline
14+ </OutButton>
15+);
src/components/FooterButton.js+2−2
@@ -3,13 +3,13 @@ import { Grid, Button } from 'indigo-react';
33
44import Footer from './Footer';
55
6−export default function FooterButton(props) {
6+export default function FooterButton({ as: As = Button, ...props }) {
77 return (
88 <Footer>
99 <Grid className="pt2">
1010 <Grid.Divider />
1111 <Grid.Item full>
12− <Button {...props} />
12+ <As {...props} />
1313 </Grid.Item>
1414 </Grid>
1515 </Footer>
src/components/Inputs.js+11
@@ -141,6 +141,17 @@ export function usePointInput({ initialValue = '~', ...rest }) {
141141 };
142142}
143143
144+export function useCheckboxInput({ initialValue, ...rest }) {
145+ return firstOf(
146+ useForm([
147+ {
148+ type: 'checkbox',
149+ ...rest,
150+ },
151+ ])
152+ );
153+}
154+
144155const kEmailValidators = [validateEmail, validateNotEmpty];
145156export const buildEmailInputConfig = extra => ({
146157 type: 'email',
src/components/Passport.js+57−6
@@ -2,7 +2,7 @@ import React from 'react';
22import cn from 'classnames';
33import Maybe from 'folktale/maybe';
44import * as ob from 'urbit-ob';
5−import { Grid, Flex } from 'indigo-react';
5+import { Grid, Flex, Text } from 'indigo-react';
66import { times } from 'lodash';
77
88import usePointBirthday from 'lib/usePointBirthday';
@@ -16,13 +16,33 @@ const buildDate = char =>
1616const kDateA = buildDate(kLoadingCharacter);
1717const kDateB = buildDate(kInterstitialCharacter);
1818
19+const kTicketCharacterLength = 40;
20+const kPendingTicket = times(
21+ kTicketCharacterLength,
22+ () => kLoadingCharacter
23+).join('');
24+const kValidTicket =
25+ times(kTicketCharacterLength, () => kLoadingCharacter).join('') + '◆';
26+
27+const kVisibleAddressCharacters = 20;
28+const kEmptyHalfAddress = times(kVisibleAddressCharacters, () => '<').join('');
29+const bufferAddressCharacters = times(40, () => '<').join('');
30+
1931/**
2032 * point is Maybe<number>
33+ * ticket is boolean
34+ * address is null | Maybe<string>
2135 */
22−export default function Passport({ point, className }) {
36+export default function Passport({
37+ point,
38+ className,
39+ ticket = null,
40+ address = null,
41+ mini = false,
42+}) {
2343 const birthday = usePointBirthday(point.getOrElse(null));
2444 const visualBirthday = birthday.matchWith({
25− Nothing: () => <Blinky a={kDateA} b={kDateB} />,
45+ Nothing: () => <Blinky a={kDateA} b={kDateB} delayed />,
2646 Just: p => formatDots(p.value),
2747 });
2848
@@ -35,18 +55,49 @@ export default function Passport({ point, className }) {
3555 Just: p => p.value,
3656 });
3757
58+ const showTicket = ticket !== null;
59+ const showValidTicket = !!ticket;
60+ const visibleTicket = showValidTicket ? kValidTicket : kPendingTicket;
61+ const visibleAddress =
62+ address &&
63+ `${address.matchWith({
64+ Nothing: () => kEmptyHalfAddress,
65+ Just: p => p.value.slice(0, kVisibleAddressCharacters),
66+ })}${bufferAddressCharacters}`;
67+
3868 return (
39− <Grid gap={16} className={cn('bg-black r8 p7', className)}>
69+ <Grid gap={4} className={cn('bg-black r8 p7', className)}>
4070 <Grid.Item cols={[1, 4]} className="bg-green">
4171 <MaybeSigil patp={name} size={64} margin={0} />
4272 </Grid.Item>
4373 <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
4474 <Flex.Item className="mono white f5">{visualName}</Flex.Item>
4575 <Flex.Item as={Flex} col>
46− <Flex.Item className="mono gray4 f6 uppercase">Birth Time</Flex.Item>
47− <Flex.Item className="mono gray4 f6">{visualBirthday}</Flex.Item>
76+ <Flex.Item as={Text} className="mono gray4 uppercase">
77+ Birth Time
78+ </Flex.Item>
79+ <Flex.Item as={Text} className="mono gray4">
80+ {visualBirthday}
81+ </Flex.Item>
4882 </Flex.Item>
4983 </Grid.Item>
84+ {!mini && showTicket && (
85+ <Grid.Item full className="mv3" as={Flex} col>
86+ <Flex.Item as={Text} className="mono gray4 uppercase">
87+ Master Ticket
88+ </Flex.Item>
89+ <Flex.Item
90+ as={Text}
91+ className={cn('mono gray4', {
92+ green3: showValidTicket,
93+ })}>
94+ {visibleTicket}
95+ </Flex.Item>
96+ <Flex.Item as={Text} className="mono gray4">
97+ {visibleAddress}
98+ </Flex.Item>
99+ </Grid.Item>
100+ )}
50101 </Grid>
51102 );
52103}
src/indigo-react/components/AccessoryIcon.js+1−1
@@ -3,7 +3,7 @@ import React from 'react';
33import Flex from './Flex';
44
55const PENDING_ACCESSORY = '⋯';
6−const SUCCESS_ACCESSORY = '✔';
6+const SUCCESS_ACCESSORY = '✓';
77const FAILURE_ACCESSORY = '×';
88
99function AccessoryIcon({ ...props }) {
src/indigo-react/components/Button.js+1−1
@@ -24,7 +24,7 @@ export default function Button({
2424 return (
2525 <Grid
2626 as="a"
27− gap={4}
27+ gap={1}
2828 className={cn(
2929 'pointer pv4 truncate flex-row justify-between',
3030 {
src/indigo-react/components/CheckboxInput.jsnew file+82
@@ -0,0 +1,82 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+import Flex from './Flex';
5+
6+// TODO: make this look like screens
7+export default function CheckboxInput({
8+ // visuals
9+ name,
10+ label,
11+ className,
12+
13+ // callbacks
14+ onValue,
15+ onPass,
16+ onError,
17+ onFocus,
18+ onEnter,
19+
20+ // state from hook
21+ focused,
22+ pass,
23+ visiblyPassed,
24+ error,
25+ data,
26+ bind,
27+ autoFocus,
28+ disabled,
29+
30+ // ignored
31+ initialValue,
32+ validators,
33+ transformers,
34+
35+ // extra
36+ ...rest
37+}) {
38+ return (
39+ <Flex
40+ className={className}
41+ row
42+ align="center"
43+ style={{
44+ ...(disabled && {
45+ pointerEvents: 'none',
46+ cursor: 'not-allowed',
47+ }),
48+ }}>
49+ <Flex.Item
50+ as="input"
51+ {...rest}
52+ className={cn(
53+ 'b b1 p3 outline-none',
54+ {
55+ 'bg-white': !disabled,
56+ 'bg-gray1': disabled,
57+ },
58+ {
59+ gray4: !focused,
60+ black: focused,
61+ },
62+ {
63+ 'b-green3': visiblyPassed,
64+ 'b-black': focused && !visiblyPassed,
65+ 'b-yellow3': !focused && error,
66+ 'b-gray3': !focused && !error && !visiblyPassed,
67+ }
68+ )}
69+ id={name}
70+ name={name}
71+ {...bind}
72+ />
73+ <Flex.Item
74+ as="label"
75+ className="f6 ph3 lh-tall us-none"
76+ htmlFor={name}
77+ flex>
78+ {label}
79+ </Flex.Item>
80+ </Flex>
81+ );
82+}
src/indigo-react/components/Grid.js+7−2
@@ -44,14 +44,19 @@ Grid.Item = function GridItem({
4444 );
4545};
4646
47−Grid.Divider = function GridDivider({ vertical = false, className, ...rest }) {
47+Grid.Divider = function GridDivider({
48+ vertical = false,
49+ color = 'gray2',
50+ className,
51+ ...rest
52+}) {
4853 return (
4954 <div
5055 style={{
5156 height: vertical ? 'auto' : '1px',
5257 width: vertical ? '1px' : 'auto',
5358 }}
54− className={cn('full', className || 'bg-gray2')}
59+ className={cn('full', `bg-${color}`, className)}
5560 {...rest}
5661 />
5762 );
src/indigo-react/components/Input.js+10−10
@@ -68,9 +68,16 @@ export default function Input({
6868 onFocus && onFocus(focused);
6969 }, [focused]); // eslint-disable-line react-hooks/exhaustive-deps
7070
71− console.log(name, visiblyPassed, error);
7271 return (
73− <Flex className={className} col>
72+ <Flex
73+ className={className}
74+ col
75+ style={{
76+ ...(disabled && {
77+ pointerEvents: 'none',
78+ cursor: 'not-allowed',
79+ }),
80+ }}>
7481 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
7582 {label}
7683 </Flex.Item>
@@ -78,9 +85,8 @@ export default function Input({
7885 <Flex.Item
7986 as="input"
8087 {...rest}
81− cols={[1, accessory ? 11 : 13]}
8288 className={cn(
83− 'rel b b1 p3 outline-none',
89+ 'b b1 p3 outline-none',
8490 { mono },
8591 {
8692 'bg-white': !disabled,
@@ -98,12 +104,6 @@ export default function Input({
98104 }
99105 // TODO: inputClassName ?
100106 )}
101− style={{
102− ...(disabled && {
103− pointerEvents: 'none',
104− cursor: 'not-allowed',
105− }),
106− }}
107107 id={name}
108108 name={name}
109109 onKeyPress={onKeyPress}
src/indigo-react/components/Typography.js+1
@@ -14,6 +14,7 @@ export const H5 = typeWith('h5');
1414export const H6 = typeWith('h6');
1515export const Caption = typeWith('caption');
1616export const P = typeWith('p');
17+export const B = typeWith('b');
1718
1819export const Breadcrumb = typeWith('span', 'f6');
1920export const Text = typeWith('span', 'f5');
src/indigo-react/index.js+1
@@ -2,6 +2,7 @@ export { default as IndigoApp } from './components/IndigoApp';
22export { default as Grid } from './components/Grid';
33export { default as Flex } from './components/Flex';
44export { default as Input } from './components/Input';
5+export { default as CheckboxInput } from './components/CheckboxInput';
56export { default as AccessoryIcon } from './components/AccessoryIcon';
67export { default as Button } from './components/Button';
78export { default as IconButton } from './components/IconButton';
src/indigo-react/indigo.css+27−12
@@ -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: Mon Jun 24 2019 19:57:25 GMT-0700 (Pacific Daylight Time)
16+ * Timestamp: Thu Jun 27 2019 14:14:25 GMT-0700 (Pacific Daylight Time)
1717 *******************************************************************************/
1818/**********************
1919Color variables
@@ -2032,30 +2032,45 @@ caption,
20322032 .fourth-4-lg {
20332033 grid-area: auto / 10 / auto / 13; } }
20342034
2035−.gap1 {
2036− gap: 1px; }
2037−
2038−.gap2 {
2039− gap: 2px; }
2035+.gap0 {
2036+ gap: 0px; }
20402037
2041−.gap4 {
2038+.gap1 {
20422039 gap: 4px; }
20432040
2044−.gap8 {
2041+.gap2 {
20452042 gap: 8px; }
20462043
2047−.gap12 {
2044+.gap3 {
20482045 gap: 12px; }
20492046
2050−.gap16 {
2047+.gap4 {
20512048 gap: 16px; }
20522049
2053−.gap24 {
2050+.gap5 {
2051+ gap: 20px; }
2052+
2053+.gap6 {
20542054 gap: 24px; }
20552055
2056−.gap32 {
2056+.gap7 {
20572057 gap: 32px; }
20582058
2059+.gap8 {
2060+ gap: 48px; }
2061+
2062+.gap9 {
2063+ gap: 64px; }
2064+
2065+.gap10 {
2066+ gap: 96px; }
2067+
2068+.gap11 {
2069+ gap: 160px; }
2070+
2071+.gap12 {
2072+ gap: 288px; }
2073+
20592074.c1-2 {
20602075 grid-column-start: 1;
20612076 grid-column-end: 2; }
src/indigo-react/lib/useForm.js+14−2
@@ -70,10 +70,21 @@ export default function useForm(configs = []) {
7070 [byName]
7171 );
7272
73+ const getValue = useCallback(
74+ (name, e) => {
75+ if (byName[name].type === 'checkbox') {
76+ return e.target.checked;
77+ }
78+
79+ return e.target.value;
80+ },
81+ [byName]
82+ );
83+
7384 // on change, transform and set value
7485 const onChange = useCallback(
75− name => e => setValue(name, transform(name, e.target.value)), //
76− [setValue, transform]
86+ name => e => setValue(name, transform(name, getValue(name, e))), //
87+ [setValue, transform, getValue]
7788 );
7889
7990 // on focus, update focus
@@ -155,6 +166,7 @@ export default function useForm(configs = []) {
155166 // dom properties below:
156167 bind: {
157168 value: values[name] || initialValue,
169+ checked: !!values[name],
158170 onChange: onChange(name),
159171 onFocus: onFocus(name),
160172 onBlur: onBlur(name),
src/lib/constants.js+3
@@ -5,6 +5,8 @@ const MAX_STAR = 65535;
55const MIN_PLANET = 65536;
66const MAX_PLANET = 4294967297;
77
8+const ZOD = MIN_GALAXY;
9+
810const PLANET_ENTROPY_BITS = 64;
911const STAR_ENTROPY_BITS = 128;
1012const GALAXY_ENTROPY_BITS = 384;
@@ -56,4 +58,5 @@ export {
5658 STAR_ENTROPY_BITS,
5759 GALAXY_ENTROPY_BITS,
5860 SEED_ENTROPY_BITS,
61+ ZOD,
5962};
src/lib/keys.js+1−1
@@ -73,7 +73,7 @@ const attemptNetworkSeedDerivation = async (next, args) => {
7373 //
7474 // following code is intentionally verbose for sake of clarity
7575
76− const point = need.pointCursor(args.pointCursor);
76+ const point = need.point(args.pointCursor);
7777 const pointDetails = need.fromPointCache(args.pointCache, point);
7878
7979 const revision =
src/lib/need.js+27−12
@@ -3,34 +3,49 @@
33
44import { BRIDGE_ERROR } from './error';
55
6−const needBuilder = error => obj => {
6+const needBuilder = fn => obj => {
77 if (!obj) {
8− throw error;
8+ fn();
99 }
1010
1111 return obj.matchWith({
12− Nothing: () => {
13− throw error;
14− },
12+ Nothing: fn,
1513 Just: p => p.value,
1614 });
1715};
1816
19−export const web3 = needBuilder(BRIDGE_ERROR.MISSING_WEB3);
20−export const contracts = needBuilder(BRIDGE_ERROR.MISSING_CONTRACTS);
21−export const wallet = needBuilder(BRIDGE_ERROR.MISSING_WALLET);
17+export const web3 = needBuilder(() => {
18+ throw new Error(BRIDGE_ERROR.MISSING_WEB3.message);
19+});
20+
21+export const contracts = needBuilder(() => {
22+ throw new Error(BRIDGE_ERROR.MISSING_CONTRACTS.message);
23+});
24+
25+export const wallet = needBuilder(() => {
26+ throw new Error(BRIDGE_ERROR.MISSING_WALLET.message);
27+});
28+
2229export const addressFromWallet = obj => wallet(obj).address;
23−export const pointCursor = needBuilder(BRIDGE_ERROR.MISSING_POINT);
24−export const pointCache = needBuilder(BRIDGE_ERROR.MISSING_POINT);
30+
31+export const point = needBuilder(() => {
32+ throw new Error(BRIDGE_ERROR.MISSING_POINT.message);
33+});
34+
35+export const pointCache = needBuilder(() => {
36+ throw new Error(BRIDGE_ERROR.MISSING_POINT.message);
37+});
38+
2539export const fromPointCache = (cache, point) => {
2640 if (!(point in cache)) {
27− throw BRIDGE_ERROR.MISSING_POINT;
41+ throw new Error(BRIDGE_ERROR.MISSING_POINT.message);
2842 }
2943
3044 return cache[point];
3145};
46+
3247export const keystore = obj => {
33− const ks = needBuilder(BRIDGE_ERROR.MISSING_KEYSTORE)();
48+ const ks = needBuilder(BRIDGE_ERROR.MISSING_KEYSTORE)(obj);
3449 return ks.value.matchWith({
3550 Ok: result => result.value,
3651 Error: _ => {
src/lib/useRouter.js+1−5
@@ -62,12 +62,8 @@ export default function useRouter({
6262 const data = useMemo(() => last(routes).data, [routes]);
6363 const Route = useMemo(() => views[peek().key], [views, peek]);
6464
65− // Scroll to top of page with each route transition on primary navigation
65+ // Scroll to top of page with each route transition
6666 useEffect(() => {
67− if (!primary) {
68− return;
69− }
70−
7167 window.scrollTo(0, 0);
7268 }, [routes, primary]);
7369
src/lib/validators.js+1−1
@@ -29,7 +29,7 @@ export const simpleValidatorWrapper = ({
2929 // Run the validator and return the result.
3030 const pass = validator(prevMessage.data);
3131 const data = pass ? transform(prevMessage.data) : prevMessage.data;
32− console.log(pass, data);
32+
3333 return newMessage(data, pass, !pass && error);
3434};
3535
src/lib/walletgen.js+5−2
@@ -10,6 +10,7 @@ import {
1010 GALAXY_ENTROPY_BITS,
1111 STAR_ENTROPY_BITS,
1212 PLANET_ENTROPY_BITS,
13+ ZOD,
1314} from './constants';
1415
1516const SEED_LENGTH_BYTES = SEED_ENTROPY_BITS / 8;
@@ -66,10 +67,12 @@ export const generateWallet = async (point, ticket, boot) => {
6667export const generateOwnershipWallet = (ship, ticket) =>
6768 kg.generateOwnershipWallet({ ship, ticket });
6869
70+// ~zod is a constant in temporary wallets used for invitations
71+export const generateTemporaryOwnershipWallet = ticket =>
72+ generateOwnershipWallet(ZOD, ticket);
6973export const generateTemporaryTicketAndWallet = async point => {
7074 const ticket = await makeTicket(point);
71− // ~zod is used as a constant here
72− const owner = await generateOwnershipWallet(0, ticket);
75+ const owner = await generateOwnershipWallet(ZOD, ticket);
7376
7477 return { ticket, owner };
7578};
src/store/lib/useBirthdaysStore.js+6
@@ -45,6 +45,12 @@ export default function useBirthdayStore() {
4545 addToBirthdayCache({
4646 [point]: Maybe.Just(new Date(block.timestamp * 1000)),
4747 });
48+ } else {
49+ // TODO: better encoding for "no birthday" state?
50+ // if there's no birthday, just use today
51+ addToBirthdayCache({
52+ [point]: Maybe.Just(new Date()),
53+ });
4854 }
4955 },
5056 [contracts, web3, addToBirthdayCache, getBirthday]
src/style/indigo-proposals.scss+4
@@ -31,3 +31,7 @@
3131.outline-none {
3232 outline: none;
3333}
34+
35+.us-none {
36+ user-select: none;
37+}
src/views/AcceptTransfer.js+2−2
@@ -25,7 +25,7 @@ class AcceptTransfer extends React.Component {
2525 super(props);
2626
2727 const receivingAddress = need.addressFromWallet(props.wallet);
28− const incomingPoint = need.pointCursor(props.pointCursor);
28+ const incomingPoint = need.point(props.pointCursor);
2929
3030 this.state = {
3131 receivingAddress: receivingAddress,
@@ -46,7 +46,7 @@ class AcceptTransfer extends React.Component {
4646 const { state, props } = this;
4747
4848 const validContracts = need.contracts(props.contracts);
49− const validPoint = need.pointCursor(props.pointCursor);
49+ const validPoint = need.point(props.pointCursor);
5050 //TODO state.incomingPoint ?
5151
5252 return Just(
src/views/Activate.js+29−51
@@ -1,61 +1,39 @@
1−import React, { useCallback } from 'react';
2−import * as ob from 'urbit-ob';
3−import Maybe from 'folktale/maybe';
4−import useImpliedPoint from 'lib/useImpliedPoint';
5−import { usePointInput, useTicketInput } from 'components/Inputs';
6−import { Grid, Input } from 'indigo-react';
1+import React from 'react';
72
8−import View from 'components/View';
9−import Footer from 'components/Footer';
10−import { ForwardButton } from 'components/Buttons';
11−import Passport from 'components/Passport';
3+import useRouter from 'lib/useRouter';
124
13−import { useHistory } from 'store/history';
5+import { ActivateFlowProvider } from './Activate/ActivateFlow';
6+import useActivateFlowState from './Activate/useActivateFlowState';
7+import ActivateCode from './Activate/ActivateCode';
8+import ActivateDisclaimer from './Activate/ActivateDisclaimer';
9+import { LocalRouterProvider } from 'lib/LocalRouter';
10+import ActivatePassport from './Activate/ActivatePassport';
1411
15−import { ROUTE_NAMES } from 'lib/routeNames';
16−import { useSyncKnownPoints } from 'lib/useSyncPoints';
17−import patp2dec from 'lib/patp2dec';
12+const kActivateNames = {
13+ CODE: 'CODE',
14+ DISCLAIMER: 'DISCLAIMER',
15+ PASSPORT: 'PASSPORT',
16+};
1817
19−export default function Activate() {
20− const { popAndPush } = useHistory();
21− const impliedPoint = useImpliedPoint();
22−
23− const pointInput = usePointInput({
24− name: 'point',
25− initialValue: impliedPoint || '',
26− autoFocus: !!impliedPoint,
27− });
28−
29− // Maybe<number>
30− const point = ob.isValidPatp(pointInput.data)
31− ? Maybe.Just(patp2dec(pointInput.data))
32− : Maybe.Nothing();
18+const kActivateViews = {
19+ [kActivateNames.CODE]: ActivateCode,
20+ [kActivateNames.DISCLAIMER]: ActivateDisclaimer,
21+ [kActivateNames.PASSPORT]: ActivatePassport,
22+};
3323
34− useSyncKnownPoints([point.getOrElse(null)].filter(p => p !== null));
35−
36− const ticketInput = useTicketInput({
37− name: 'ticket',
24+export default function Activate() {
25+ const state = useActivateFlowState();
26+ const { Route, ...router } = useRouter({
27+ names: kActivateNames,
28+ views: kActivateViews,
29+ initialRoutes: [{ key: kActivateNames.CODE }],
3830 });
3931
40− const goToLogin = useCallback(() => popAndPush(ROUTE_NAMES.LOGIN), [
41− popAndPush,
42− ]);
43−
4432 return (
45− <View>
46− <Grid>
47− <Grid.Item as={Passport} point={point} full />
48− {!impliedPoint && <Grid.Item as={Input} {...pointInput} full />}
49− <Grid.Item as={Input} {...ticketInput} full />
50− </Grid>
51− <Footer>
52− <Grid className="pt2">
53− <Grid.Divider />
54− <Grid.Item as={ForwardButton} onClick={goToLogin} full>
55− Login
56− </Grid.Item>
57− </Grid>
58− </Footer>
59− </View>
33+ <LocalRouterProvider value={router}>
34+ <ActivateFlowProvider value={state}>
35+ <Route />
36+ </ActivateFlowProvider>
37+ </LocalRouterProvider>
6038 );
6139}
src/views/Activate/ActivateCode.jsnew file+163
@@ -0,0 +1,163 @@
1+import React, { useCallback, useEffect, useState } from 'react';
2+import * as azimuth from 'azimuth-js';
3+import Maybe from 'folktale/maybe';
4+import { useTicketInput } from 'components/Inputs';
5+import { Grid, Input, H4, ErrorText } from 'indigo-react';
6+
7+import View from 'components/View';
8+import { ForwardButton, OfflineButton } from 'components/Buttons';
9+import Passport from 'components/Passport';
10+
11+import { useHistory } from 'store/history';
12+
13+import * as need from 'lib/need';
14+import { ROUTE_NAMES } from 'lib/routeNames';
15+import { useSyncKnownPoints } from 'lib/useSyncPoints';
16+import FooterButton from 'components/FooterButton';
17+import Blinky from 'components/Blinky';
18+import { DEFAULT_HD_PATH, walletFromMnemonic } from 'lib/wallet';
19+import { useNetwork } from 'store/network';
20+import { generateWallet } from 'lib/invite';
21+import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
22+import { useActivateFlow } from './ActivateFlow';
23+import { useLocalRouter } from 'lib/LocalRouter';
24+
25+export default function ActivateCode() {
26+ const history = useHistory();
27+ const { names, push } = useLocalRouter();
28+ const { contracts } = useNetwork();
29+ const [generalError, setGeneralError] = useState();
30+ const [deriving, setDeriving] = useState(false);
31+ const {
32+ derivedWallet,
33+ setDerivedWallet,
34+ setInviteWallet,
35+ derivedPoint,
36+ setDerivedPoint,
37+ } = useActivateFlow();
38+
39+ const ticketInput = useTicketInput({
40+ name: 'ticket',
41+ label: 'Code',
42+ autoFocus: true,
43+ });
44+ const validTicket = ticketInput.pass;
45+
46+ const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
47+ history,
48+ ]);
49+ const goToDisclaimer = useCallback(() => push(names.DISCLAIMER), [
50+ names,
51+ push,
52+ ]);
53+
54+ const pass = derivedWallet.matchWith({
55+ Nothing: () => false,
56+ Just: () => true,
57+ });
58+
59+ useEffect(() => {
60+ if (validTicket) {
61+ const _contracts = need.contracts(contracts);
62+
63+ setDeriving(true);
64+ // when the ticket becomes valid, derive the point
65+ (async () => {
66+ const { seed } = await generateTemporaryOwnershipWallet(
67+ ticketInput.data
68+ );
69+
70+ //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+ });
81+
82+ const incoming = await azimuth.azimuth.getTransferringFor(
83+ _contracts,
84+ inviteWallet.address
85+ );
86+
87+ let realPoint = Maybe.Nothing();
88+ let realWallet = Maybe.Nothing();
89+
90+ if (incoming.length > 0) {
91+ let pointNum = parseInt(incoming[0], 10);
92+ realPoint = Maybe.Just(pointNum);
93+
94+ const genWallet = await generateWallet(pointNum);
95+
96+ realWallet = Maybe.Just(genWallet);
97+ if (incoming.length > 1) {
98+ setGeneralError(
99+ 'This invite code has multiple points available.\n' +
100+ "Once you've activated this point, activate the next with the same process."
101+ );
102+ }
103+ } else {
104+ setGeneralError(
105+ 'Invite code has no claimable point.\n' +
106+ 'Check your invite code and try again?'
107+ );
108+ }
109+
110+ setDerivedPoint(realPoint);
111+ setDerivedWallet(realWallet);
112+ setInviteWallet(Maybe.Just(inviteWallet));
113+ setDeriving(false);
114+ })();
115+ } else {
116+ setGeneralError(false);
117+ }
118+ }, [
119+ validTicket,
120+ contracts,
121+ ticketInput.data,
122+ setDerivedPoint,
123+ setDerivedWallet,
124+ setInviteWallet,
125+ ]);
126+
127+ // when we know the derived point, ensure we have the data to display it
128+ useSyncKnownPoints([derivedPoint.getOrElse(null)].filter(p => p !== null));
129+
130+ return (
131+ <View>
132+ <Grid>
133+ <Grid.Item as={Passport} point={derivedPoint} full />
134+ <Grid.Item as={H4} className="mt3 mb2" full>
135+ Activate
136+ </Grid.Item>
137+ <Grid.Item as={Input} {...ticketInput} full />
138+ {generalError && (
139+ <Grid.Item full>
140+ <ErrorText>{generalError}</ErrorText>{' '}
141+ </Grid.Item>
142+ )}
143+ <Grid.Item
144+ as={ForwardButton}
145+ className="mt4"
146+ disabled={!pass || deriving}
147+ accessory={deriving ? <Blinky /> : null}
148+ onClick={goToDisclaimer}
149+ solid
150+ full>
151+ {deriving && 'Deriving...'}
152+ {!deriving && 'Go'}
153+ </Grid.Item>
154+ <Grid.Divider className="mt3" />
155+ <Grid.Item as={ForwardButton} onClick={goToLogin} full>
156+ Login
157+ </Grid.Item>
158+ <Grid.Divider />
159+ </Grid>
160+ <FooterButton as={OfflineButton} />
161+ </View>
162+ );
163+}
src/views/Activate/ActivateDisclaimer.jsnew file+83
@@ -0,0 +1,83 @@
1+import React, { useCallback } from 'react';
2+import { Grid, H3, P, B, Flex, Text, CheckboxInput } from 'indigo-react';
3+
4+import View from 'components/View';
5+import { useCheckboxInput } from 'components/Inputs';
6+import { ForwardButton } from 'components/Buttons';
7+import { useLocalRouter } from 'lib/LocalRouter';
8+
9+export default function ActivateDisclaimer() {
10+ const { push, names } = useLocalRouter();
11+ const checkboxInput = useCheckboxInput({
12+ name: 'checkbox',
13+ label: 'I acknowledge and understand these rights',
14+ });
15+
16+ const goToPassport = useCallback(() => push(names.PASSPORT), [push, names]);
17+
18+ return (
19+ <View>
20+ <Grid gap={2} className="mt8 mb10">
21+ <Grid.Item as={H3} full>
22+ Disclaimer
23+ </Grid.Item>
24+ <Grid.Item full>
25+ <P>
26+ Welcome to Bridge. Please read this information. Your assets could
27+ be stolen if you do not pay attention to these warnings.
28+ </P>
29+
30+ <P>
31+ <B>You own and control your assets. We do not.</B> If you send your
32+ assets to another address, we can’t get them back for you.
33+ </P>
34+
35+ <B>Please understand that nobody can:</B>
36+ <P>
37+ 1. Access your assets for you <br />
38+ 2. Recover, reset, or modify ANY of your information <br />
39+ 3. Reverse, cancel, or refund transactions <br />
40+ 4. Hold, back-up, or access your keys
41+ </P>
42+
43+ <B>
44+ You’re responsible for keeping your information safe. This includes:
45+ </B>
46+ <P>
47+ 1. Private Keys/Mnemonic Phrases and passwords <br />
48+ 2. JSON files <br />
49+ 3. Hardware wallet PINs; <br />
50+ 4. Downloading this software from a reliable site, and ensuring it
51+ has not been tampered with. [add something about using the checksum
52+ here]
53+ </P>
54+
55+ <B>To be clear</B>
56+ <P>
57+ This software is provided “as is” without warranties of any kind and
58+ our liability to you in connection with the same is limited;
59+ </P>
60+ <P>
61+ We have no liability for any security problems or incidents that you
62+ may experience, including any loss or theft of your keys or any
63+ problems that may arise in connection with your wallet.
64+ </P>
65+ </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>
70+ </Grid.Item>
71+ <Grid.Item as={CheckboxInput} {...checkboxInput} full />
72+ <Grid.Item
73+ as={ForwardButton}
74+ disabled={!checkboxInput.data}
75+ onClick={goToPassport}
76+ solid
77+ full>
78+ Continue
79+ </Grid.Item>
80+ </Grid>
81+ </View>
82+ );
83+}
src/views/Activate/ActivateFlow.jsnew file+9
@@ -0,0 +1,9 @@
1+import { createContext, useContext } from 'react';
2+
3+export const ActivateFlowContext = createContext(null);
4+export const ActivateFlowProvider = ActivateFlowContext.Provider;
5+
6+// Hook version
7+export function useActivateFlow() {
8+ return useContext(ActivateFlowContext);
9+}
src/views/Activate/ActivatePassport.jsnew file+82
@@ -0,0 +1,82 @@
1+import React, { useCallback, useState } 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+import { useLocalRouter } from 'lib/LocalRouter';
8+
9+import View from 'components/View';
10+import { ForwardButton, DownloadButton } from 'components/Buttons';
11+import Passport from 'components/Passport';
12+
13+import { useActivateFlow } from './ActivateFlow';
14+import { downloadWallet } from 'lib/invite';
15+
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);
21+
22+ const [paper, setPaper] = useState(Maybe.Nothing());
23+
24+ const goToPassport = useCallback(() => push(names.PASSPORT), [push, names]);
25+
26+ const pointAsString = derivedPoint.matchWith({
27+ Nothing: () => '',
28+ Just: p => p.value.toFixed(),
29+ });
30+
31+ const generated = paper.matchWith({
32+ Nothing: () => false,
33+ Just: () => true,
34+ });
35+
36+ const download = useCallback(() => downloadWallet(paper.getOrElse([])), [
37+ paper,
38+ ]);
39+
40+ 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
68+ </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>
81+ );
82+}
src/views/Activate/useActivateFlowState.jsnew file+17
@@ -0,0 +1,17 @@
1+import { useState } from 'react';
2+import Maybe from 'folktale/maybe';
3+
4+export default function useActivateFlowState() {
5+ const [derivedWallet, setDerivedWallet] = useState(Maybe.Nothing());
6+ const [inviteWallet, setInviteWallet] = useState(Maybe.Nothing());
7+ const [derivedPoint, setDerivedPoint] = useState(Maybe.Nothing());
8+
9+ return {
10+ derivedWallet,
11+ setDerivedWallet,
12+ inviteWallet,
13+ setInviteWallet,
14+ derivedPoint,
15+ setDerivedPoint,
16+ };
17+}
src/views/CancelTransfer.js+2−2
@@ -17,7 +17,7 @@ class CancelTransfer extends React.Component {
1717 constructor(props) {
1818 super(props);
1919
20− const pointInTransfer = need.pointCursor(props.pointCursor);
20+ const pointInTransfer = need.point(props.pointCursor);
2121
2222 this.state = {
2323 proxyAddress: '',
@@ -31,7 +31,7 @@ class CancelTransfer extends React.Component {
3131 const { props } = this;
3232
3333 const validContracts = need.contracts(props.contracts);
34− const validPoint = need.pointCursor(props.pointCursor);
34+ const validPoint = need.point(props.pointCursor);
3535 //TODO this.state.pointInTransfer ?
3636
3737 const txn = azimuth.ecliptic.setTransferProxy(
src/views/GenKeyfile.js+1−1
@@ -27,7 +27,7 @@ class GenKeyfile extends React.Component {
2727 }
2828
2929 getPointDetails() {
30− const point = need.pointCursor(this.props.pointCursor);
30+ const point = need.point(this.props.pointCursor);
3131 const pointDetails = need.fromPointCache(this.props.pointCache, point);
3232
3333 // in case we did SetKeys earlier this session, make sure to generate the
src/views/Invite.js+1−1
@@ -37,7 +37,7 @@ export default function Invite() {
3737 initialRoutes: [{ key: kInviteNames.URL }],
3838 });
3939 const { pointCursor } = usePointCursor();
40− const point = need.pointCursor(pointCursor);
40+ const point = need.point(pointCursor);
4141 const name = useCurrentPointName();
4242
4343 // sync the current point
src/views/Invite/InviteEmail.js+3−3
@@ -100,7 +100,7 @@ export default function InviteEmail() {
100100 const { wallet, walletType, walletHdPath } = useWallet();
101101 const { syncInvites, getInvites } = usePointCache();
102102 const { pointCursor } = usePointCursor();
103− const point = need.pointCursor(pointCursor);
103+ const point = need.point(pointCursor);
104104 const { getHasRecieved, syncHasReceivedForEmail, sendMail } = useMailer();
105105
106106 const { availableInvites } = getInvites(point);
@@ -391,7 +391,7 @@ export default function InviteEmail() {
391391 }, [isDone, syncInvites, point]);
392392
393393 return (
394− <Grid gap={12}>
394+ <Grid gap={3}>
395395 <Grid.Item as={Grid} full>
396396 <Grid.Item as={Flex} cols={[1, 11]} align="center">
397397 <MiniBackButton onClick={() => pop()} />
@@ -432,7 +432,7 @@ export default function InviteEmail() {
432432 <Grid.Item
433433 key={input.name}
434434 as={Grid}
435− gap={12}
435+ gap={3}
436436 onMouseOver={() => setHovered({ [input.name]: true })}
437437 onMouseLeave={() => setHovered({ [input.name]: false })}
438438 full>
src/views/Invite/InviteUrl.js+1−1
@@ -13,7 +13,7 @@ import FooterButton from 'components/FooterButton';
1313export default function InviteUrl() {
1414 const { names, push } = useLocalRouter();
1515 const { pointCursor } = usePointCursor();
16− const point = need.pointCursor(pointCursor);
16+ const point = need.point(pointCursor);
1717 const { availableInvites } = useInvites(point);
1818 const canInvite = availableInvites.matchWith({
1919 Nothing: () => false,
src/views/InvitesManage.js+1−1
@@ -39,7 +39,7 @@ class InvitesManage extends React.Component {
3939 }
4040
4141 componentDidMount() {
42− this.point = need.pointCursor(this.props.pointCursor);
42+ this.point = need.point(this.props.pointCursor);
4343 this.contracts = need.contracts(this.props.contracts);
4444
4545 azimuth.azimuth
src/views/IssueChild.js+1−1
@@ -40,7 +40,7 @@ class IssueChild extends React.Component {
4040 constructor(props) {
4141 super(props);
4242
43− const issuingPoint = need.pointCursor(props.pointCursor);
43+ const issuingPoint = need.point(props.pointCursor);
4444
4545 const getCandidate = () => ob.patp(getSpawnCandidate(issuingPoint));
4646
src/views/Point.js+1−1
@@ -24,7 +24,7 @@ export default function Point() {
2424 const { pointCursor } = usePointCursor();
2525 const { wallet } = useWallet();
2626
27− const point = need.pointCursor(pointCursor);
27+ const point = need.point(pointCursor);
2828
2929 // fetch the invites for the current cursor
3030 const { availableInvites } = useInvites(point);
src/views/Point/Actions.js+1−1
@@ -19,7 +19,7 @@ function Actions() {
1919 const history = useHistory();
2020 const { wallet } = useWallet();
2121 const { pointCursor } = usePointCursor();
22− const point = need.pointCursor(pointCursor);
22+ const point = need.point(pointCursor);
2323 const { getDetails } = usePointCache();
2424 const pointDetails = getDetails(point);
2525
src/views/SetKeys.js+2−2
@@ -22,7 +22,7 @@ class SetKeys extends React.Component {
2222 constructor(props) {
2323 super(props);
2424
25− const point = need.pointCursor(props.pointCursor);
25+ const point = need.point(props.pointCursor);
2626
2727 this.state = {
2828 auth: '',
@@ -115,7 +115,7 @@ class SetKeys extends React.Component {
115115 const { state, props } = this;
116116
117117 const validContracts = need.contracts(props.contracts);
118− const validPoint = need.pointCursor(props.pointCursor);
118+ const validPoint = need.point(props.pointCursor);
119119
120120 // TODO: move this to a lib for validating things
121121 const hexRegExp = /[0-9A-Fa-f]{64}/g;
src/views/SetProxy.js+2−2
@@ -29,7 +29,7 @@ class _SetProxy extends React.Component {
2929 constructor(props) {
3030 super(props);
3131
32− const issuingPoint = need.pointCursor(props.pointCursor);
32+ const issuingPoint = need.point(props.pointCursor);
3333
3434 this.state = {
3535 proxyAddress: '',
@@ -61,7 +61,7 @@ class _SetProxy extends React.Component {
6161 const { state, props } = this;
6262
6363 const validContracts = need.contracts(props.contracts);
64− const validPoint = need.pointCursor(props.pointCursor);
64+ const validPoint = need.point(props.pointCursor);
6565
6666 const txArgs = [validContracts, validPoint, state.proxyAddress];
6767
src/views/Transfer.js+2−2
@@ -20,7 +20,7 @@ class Transfer extends React.Component {
2020 constructor(props) {
2121 super(props);
2222
23− const issuingPoint = need.pointCursor(props.pointCursor);
23+ const issuingPoint = need.point(props.pointCursor);
2424
2525 this.state = {
2626 receivingAddress: '',
@@ -47,7 +47,7 @@ class Transfer extends React.Component {
4747 const { state, props } = this;
4848
4949 const validContracts = need.contracts(props.contracts);
50− const validPoint = need.pointCursor(props.pointCursor);
50+ const validPoint = need.point(props.pointCursor);
5151
5252 const txn = azimuth.ecliptic.setTransferProxy(
5353 validContracts,