on master help sign in
checkpoint: initial approach to Activate page in figma
82d761dMatt Condon (shrugs)
src/Bridge.js+1−7
@@ -22,13 +22,7 @@ const kInitialNetworkType = isDevelopment
22const shouldStubLocal = process.env.REACT_APP_STUB_LOCAL === 'true';
22const shouldStubLocal = process.env.REACT_APP_STUB_LOCAL === 'true';
23const kIsStubbed = isDevelopment && shouldStubLocal;
23const kIsStubbed = isDevelopment && shouldStubLocal;
24const kInitialRoutes = kIsStubbed
24const kInitialRoutes = kIsStubbed
25− ? [
26− { key: ROUTE_NAMES.LANDING },
25+ ? [{ key: ROUTE_NAMES.LANDING }, { key: ROUTE_NAMES.ACTIVATE }]
27− { key: ROUTE_NAMES.LOGIN },
28− { key: ROUTE_NAMES.POINTS },
29− { key: ROUTE_NAMES.POINT },
30− { key: ROUTE_NAMES.INVITE },
31− ]
32 : [{ key: ROUTE_NAMES.LANDING }];
26 : [{ key: ROUTE_NAMES.LANDING }];
33
27
34const kInitialWallet = kIsStubbed
28const kInitialWallet = kIsStubbed
src/components/AspectRatio.jsnew file+22
@@ -0,0 +1,22 @@
1+import React from 'react';
2+
3+/**
4+ * A very useful Flutter widget reimplemented with
5+ * the world's dumbest css hack.
6+ *
7+ * Simply: container div with appropriate relative padding,
8+ * then absolutely pin the child.
9+ *
10+ * via: https://www.w3schools.com/howto/howto_css_aspect_ratio.asp
11+ */
12+export default function AspectRatio({ aspectRatio = 1, children }) {
13+ const percent = `${(1 / aspectRatio) * 100}%`;
14+
15+ return (
16+ <div className="rel" style={{ paddingTop: percent }}>
17+ <div className="abs" style={{ top: 0, bottom: 0, left: 0, right: 0 }}>
18+ {children}
19+ </div>
20+ </div>
21+ );
22+}
src/components/InputSigil.js+3−3
@@ -1,8 +1,8 @@
1import React from 'react';
1import React from 'react';
2−import cn from 'classnames';
3import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
4
3
5import Sigil from './Sigil';
4import Sigil from './Sigil';
5+import AspectRatio from './AspectRatio';
6
6
7const selectColorway = (pass, fail, focused) => {
7const selectColorway = (pass, fail, focused) => {
8 if (pass) {
8 if (pass) {
@@ -20,7 +20,7 @@ const selectColorway = (pass, fail, focused) => {
20 return ['#FFFFFF', '#7F7F7F'];
20 return ['#FFFFFF', '#7F7F7F'];
21};
21};
22
22
23−export default function ValidatedSigil({
23+export default function InputSigil({
24 className,
24 className,
25 patp,
25 patp,
26 size,
26 size,
@@ -35,6 +35,6 @@ export default function ValidatedSigil({
35 return valid ? (
35 return valid ? (
36 <Sigil patp={patp} size={size} colorway={colorway} margin={0} {...rest} />
36 <Sigil patp={patp} size={size} colorway={colorway} margin={0} {...rest} />
37 ) : (
37 ) : (
38− <div className={cn('bg-transparent', className)} />
38+ <AspectRatio aspectRatio={1} />
39 );
39 );
40}
40}
src/components/Inputs.js+2
@@ -92,6 +92,7 @@ export function useTicketInput({ initialValue = '~', ...rest }) {
92 useForm([
92 useForm([
93 {
93 {
94 type: 'password',
94 type: 'password',
95+ label: 'Ticket',
95 placeholder: '~master-ticket',
96 placeholder: '~master-ticket',
96 validators: kTicketValidators,
97 validators: kTicketValidators,
97 transformers: kTicketTransformers,
98 transformers: kTicketTransformers,
@@ -111,6 +112,7 @@ export function usePointInput({ initialValue = '~', ...rest }) {
111 useForm([
112 useForm([
112 {
113 {
113 type: 'text',
114 type: 'text',
115+ label: 'Point',
114 placeholder: 'e.g. ~zod',
116 placeholder: 'e.g. ~zod',
115 validators: kPointValidators,
117 validators: kPointValidators,
116 transformers: kPointTransformers,
118 transformers: kPointTransformers,
src/components/MaybeSigil.jsnew file+21
@@ -0,0 +1,21 @@
1+import React from 'react';
2+import * as ob from 'urbit-ob';
3+
4+import Sigil from './Sigil';
5+import AspectRatio from './AspectRatio';
6+
7+/**
8+ * patp is Maybe<string>
9+ */
10+export default function MaybeSigil({ className, patp, size, ...rest }) {
11+ const validPatp = patp.matchWith({
12+ Nothing: () => null,
13+ Just: p => (ob.isValidPatp(p.value) ? p.value : null),
14+ });
15+
16+ return validPatp ? (
17+ <Sigil patp={validPatp} size={size} margin={0} {...rest} />
18+ ) : (
19+ <AspectRatio aspectRatio={1} />
20+ );
21+}
src/components/Passport.js+22−9
@@ -1,4 +1,6 @@
1import React from 'react';
1import React from 'react';
2+import cn from 'classnames';
3+import Maybe from 'folktale/maybe';
2import * as ob from 'urbit-ob';
4import * as ob from 'urbit-ob';
3import { Grid, Flex } from 'indigo-react';
5import { Grid, Flex } from 'indigo-react';
4import { times } from 'lodash';
6import { times } from 'lodash';
@@ -6,32 +8,43 @@ import { times } from 'lodash';
6import usePointBirthday from 'lib/usePointBirthday';
8import usePointBirthday from 'lib/usePointBirthday';
7import { formatDots } from 'lib/dateFormat';
9import { formatDots } from 'lib/dateFormat';
8
10
9−import Sigil from './Sigil';
10import Blinky, { kLoadingCharacter, kInterstitialCharacter } from './Blinky';
11import Blinky, { kLoadingCharacter, kInterstitialCharacter } from './Blinky';
12+import MaybeSigil from './MaybeSigil';
11
13
12const buildDate = char =>
14const buildDate = char =>
13 [4, 2, 2].map(t => times(t, () => char).join('')).join('.');
15 [4, 2, 2].map(t => times(t, () => char).join('')).join('.');
14const kDateA = buildDate(kLoadingCharacter);
16const kDateA = buildDate(kLoadingCharacter);
15const kDateB = buildDate(kInterstitialCharacter);
17const kDateB = buildDate(kInterstitialCharacter);
16
18
19+/**
20+ * point is Maybe<number>
21+ */
17−export default function Passport({ point }) {
22+export default function Passport({ point, className }) {
18− const birthday = usePointBirthday(point);
23+ const birthday = usePointBirthday(point.getOrElse(null));
19− const birthDate = birthday.matchWith({
24+ const visualBirthday = birthday.matchWith({
20 Nothing: () => <Blinky a={kDateA} b={kDateB} />,
25 Nothing: () => <Blinky a={kDateA} b={kDateB} />,
21 Just: p => formatDots(p.value),
26 Just: p => formatDots(p.value),
22 });
27 });
28+
23− const name = ob.patp(point);
29+ const name = point.matchWith({
30+ Nothing: () => Maybe.Nothing(),
31+ Just: p => Maybe.Just(ob.patp(p.value)),
32+ });
33+ const visualName = name.matchWith({
34+ Nothing: () => '...',
35+ Just: p => p.value,
36+ });
24
37
25 return (
38 return (
26− <Grid gap={16} className="bg-black r8 p7">
39+ <Grid gap={16} className={cn('bg-black r8 p7', className)}>
27 <Grid.Item cols={[1, 4]} className="bg-green">
40 <Grid.Item cols={[1, 4]} className="bg-green">
28− <Sigil patp={name} size={64} margin={0} />
41+ <MaybeSigil patp={name} size={64} margin={0} />
29 </Grid.Item>
42 </Grid.Item>
30 <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
43 <Grid.Item as={Flex} cols={[4, 13]} col justify="between">
31− <Flex.Item className="mono white f5">{name}</Flex.Item>
44+ <Flex.Item className="mono white f5">{visualName}</Flex.Item>
32 <Flex.Item as={Flex} col>
45 <Flex.Item as={Flex} col>
33 <Flex.Item className="mono gray4 f6 uppercase">Birth Time</Flex.Item>
46 <Flex.Item className="mono gray4 f6 uppercase">Birth Time</Flex.Item>
34− <Flex.Item className="mono gray4 f6">{birthDate}</Flex.Item>
47+ <Flex.Item className="mono gray4 f6">{visualBirthday}</Flex.Item>
35 </Flex.Item>
48 </Flex.Item>
36 </Grid.Item>
49 </Grid.Item>
37 </Grid>
50 </Grid>
src/components/old/Header.jsdeleted−62
@@ -1,62 +0,0 @@
1−import React from 'react';
2−import { Button } from './Base';
3−
4−import { getRouteBreadcrumb } from '../../lib/router';
5−import { ROUTE_NAMES } from '../../lib/routeNames';
6−import { useHistory } from '../../store/history';
7−import { isLast } from '../../lib/lib';
8−import { usePointCursor } from '../../store/pointCursor';
9−
10−// hook to create a breadcrumb builder function based on the current
11−// global states
12−function useRouteBreadcrumbBuilder() {
13− const { pointCursor } = usePointCursor();
14−
15− return getRouteBreadcrumb({ pointCursor });
16−}
17−
18−function Crumbs(props) {
19− const history = useHistory();
20− const breadcrumbBuilder = useRouteBreadcrumbBuilder();
21−
22− return (
23− <>
24− {history.routes.map((route, idx) => {
25− return (
26− <div key={`history-${idx}`} className={'flex items-center'}>
27− <Button
28− prop-type={'link'}
29− prop-size={'sm'}
30− onClick={() => history.pop(history.size - idx - 1)}>
31− {breadcrumbBuilder(route)}
32− </Button>
33−
34− {isLast(history.size, idx) ? <div /> : '/'}
35− </div>
36− );
37− })}
38− </>
39− );
40−}
41−
42−function Header(props) {
43− const history = useHistory();
44−
45− const showCrumbs = !history.includes(ROUTE_NAMES.INVITE_TICKET);
46−
47− if (showCrumbs) {
48− return (
49− <div className={'flex items-center h-10'}>
50− <Crumbs />
51− </div>
52− );
53− }
54−
55− return (
56− <h2 className="mt-9 clickable" onClick={() => history.pop()}>
57− Bridge
58− </h2>
59− );
60−}
61−
62−export default Header;
src/indigo-react/components/Input.js+2−1
@@ -68,6 +68,7 @@ export default function Input({
68 onFocus && onFocus(focused);
68 onFocus && onFocus(focused);
69 }, [focused]); // eslint-disable-line react-hooks/exhaustive-deps
69 }, [focused]); // eslint-disable-line react-hooks/exhaustive-deps
70
70
71+ console.log(name, visiblyPassed, error);
71 return (
72 return (
72 <Flex className={className} col>
73 <Flex className={className} col>
73 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
74 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
@@ -92,7 +93,7 @@ export default function Input({
92 {
93 {
93 'b-green3': visiblyPassed,
94 'b-green3': visiblyPassed,
94 'b-black': focused && !visiblyPassed,
95 'b-black': focused && !visiblyPassed,
95− 'b-yellow3': !focused && !visiblyPassed && error,
96+ 'b-yellow3': !focused && error,
96 'b-gray3': !focused && !error && !visiblyPassed,
97 'b-gray3': !focused && !error && !visiblyPassed,
97 }
98 }
98 // TODO: inputClassName ?
99 // TODO: inputClassName ?
src/indigo-react/lib/useForm.js+1−1
@@ -85,7 +85,7 @@ export default function useForm(configs = []) {
85 const onBlur = useCallback(
85 const onBlur = useCallback(
86 name => e => {
86 name => e => {
87 setFocused({ [name]: false });
87 setFocused({ [name]: false });
88− setHasBeenFocused({ [name]: false });
88+ setHasBeenFocused({ [name]: true });
89 },
89 },
90 [setFocused, setHasBeenFocused]
90 [setFocused, setHasBeenFocused]
91 );
91 );
src/lib/patp2dec.jsnew file+9
@@ -0,0 +1,9 @@
1+import * as ob from 'urbit-ob';
2+
3+/**
4+ * Convert a patp string into a number, or throw.
5+ *
6+ * @throws
7+ * @return number
8+ */
9+export default patp => parseInt(ob.patp2dec(patp), 10);
src/lib/routeNames.js+1−1
@@ -1,7 +1,7 @@
1export const ROUTE_NAMES = {
1export const ROUTE_NAMES = {
2 DEFAULT: Symbol('DEFAULT'),
2 DEFAULT: Symbol('DEFAULT'),
3 //
3 //
4− INVITE_TICKET: Symbol('INVITE_TICKET'),
4+ ACTIVATE: Symbol('ACTIVATE'),
5 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
5 INVITE_TRANSACTIONS: Symbol('INVITE_TRANSACTIONS'),
6 INVITE: Symbol('INVITE'),
6 INVITE: Symbol('INVITE'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
7 INVITES_MANAGE: Symbol('INVITES_MANAGE'),
src/lib/router.js+2−50
@@ -1,9 +1,5 @@
1−import * as ob from 'urbit-ob';
2−import * as need from './need';
3−
4import Landing from '../views/Landing';
1import Landing from '../views/Landing';
5import Login from '../views/Login.js';
2import Login from '../views/Login.js';
6−import InviteTicket from '../views/InviteTicket';
7import InvitesManage from '../views/InvitesManage.js';
3import InvitesManage from '../views/InvitesManage.js';
8import AcceptTransfer from '../views/AcceptTransfer';
4import AcceptTransfer from '../views/AcceptTransfer';
9import CancelTransfer from '../views/CancelTransfer';
5import CancelTransfer from '../views/CancelTransfer';
@@ -22,13 +18,13 @@ import Point from '../views/Point';
22import Points from '../views/Points';
18import Points from '../views/Points';
23import Transfer from '../views/Transfer';
19import Transfer from '../views/Transfer';
24import Invite from 'views/Invite';
20import Invite from 'views/Invite';
21+import Activate from 'views/Activate';
25
22
26import { ROUTE_NAMES } from './routeNames';
23import { ROUTE_NAMES } from './routeNames';
27
24
28export const ROUTES = {
25export const ROUTES = {
29− [ROUTE_NAMES.DEFAULT]: Landing,
30 [ROUTE_NAMES.LANDING]: Landing,
26 [ROUTE_NAMES.LANDING]: Landing,
31− [ROUTE_NAMES.INVITE_TICKET]: InviteTicket,
27+ [ROUTE_NAMES.ACTIVATE]: Activate,
32 [ROUTE_NAMES.INVITE]: Invite,
28 [ROUTE_NAMES.INVITE]: Invite,
33 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
29 [ROUTE_NAMES.INVITES_MANAGE]: InvitesManage,
34 [ROUTE_NAMES.LOGIN]: Login,
30 [ROUTE_NAMES.LOGIN]: Login,
@@ -47,47 +43,3 @@ export const ROUTES = {
47 [ROUTE_NAMES.SENT_TRANSACTION]: SentTransaction,
43 [ROUTE_NAMES.SENT_TRANSACTION]: SentTransaction,
48 [ROUTE_NAMES.GEN_KEYFILE]: GenKeyfile,
44 [ROUTE_NAMES.GEN_KEYFILE]: GenKeyfile,
49};
45};
50−
51−export const getRouteBreadcrumb = ({ pointCursor }) => route => {
52− switch (route.name) {
53− case ROUTE_NAMES.INVITE_TICKET:
54− return 'Invite code';
55− case ROUTE_NAMES.INVITE_TRANSACTIONS:
56− return 'Setting up new wallet';
57− case ROUTE_NAMES.INVITE:
58− return 'Send invites';
59− case ROUTE_NAMES.INVITES_MANAGE:
60− return 'Manage invites';
61− case ROUTE_NAMES.LOGIN:
62− return 'Login to Bridge';
63− case ROUTE_NAMES.POINT:
64− return `Overview: ${ob.patp(need.pointCursor(pointCursor))}`;
65− case ROUTE_NAMES.POINTS:
66− return 'Points';
67− case ROUTE_NAMES.VIEW_POINT:
68− return 'View';
69− case ROUTE_NAMES.SET_TRANSFER_PROXY:
70− case ROUTE_NAMES.SET_MANAGEMENT_PROXY:
71− case ROUTE_NAMES.SET_SPAWN_PROXY:
72− return 'Set Proxy';
73− case ROUTE_NAMES.CREATE_GALAXY:
74− return 'Create Galaxy';
75− case ROUTE_NAMES.ISSUE_CHILD:
76− return 'Issue Child';
77− case ROUTE_NAMES.TRANSFER:
78− return 'Transfer';
79− case ROUTE_NAMES.ACCEPT_TRANSFER:
80− return 'Accept Transfer';
81− case ROUTE_NAMES.CANCEL_TRANSFER:
82− return 'Cancel Transfer';
83− case ROUTE_NAMES.GEN_KEYFILE:
84− return 'Keyfile';
85− case ROUTE_NAMES.SET_KEYS:
86− return 'Configure Keys';
87− case ROUTE_NAMES.SENT_TRANSACTION:
88− return 'Txn';
89− case ROUTE_NAMES.LANDING:
90− default:
91− return 'Bridge';
92− }
93−};
src/lib/useImpliedPoint.jsnew file+14
@@ -0,0 +1,14 @@
1+import * as ob from 'urbit-ob';
2+
3+import { prependSig } from './transformers';
4+
5+/**
6+ * pull the suggested point from the subdomain
7+ */
8+export default function useImpliedPoint() {
9+ const subdomain = window.location.host.split('.')[0];
10+ const patp = prependSig(subdomain);
11+ const isValid = ob.isValidPatp(patp);
12+
13+ return isValid ? patp : null;
14+}
src/lib/useRenderCount.js+2−2
@@ -1,7 +1,7 @@
1import { useRef } from 'react';
1import { useRef } from 'react';
2
2
3−export default function useRenderCount(debubgLabel = 'Component') {
3+export default function useRenderCount(debugLabel = 'Component') {
4 const count = useRef(0);
4 const count = useRef(0);
5 count.current++;
5 count.current++;
6− console.log(`${debubgLabel} — ${count.current}`);
6+ console.log(`${debugLabel} — ${count.current}`);
7}
7}
src/lib/useSyncPoints.js+23−30
@@ -1,39 +1,32 @@
1import { useEffect } from 'react';
1import { useEffect } from 'react';
2−import { isEqual } from 'lodash';
2+import { difference } from 'lodash';
3
3
4import { usePointCache } from 'store/pointCache';
4import { usePointCache } from 'store/pointCache';
5import usePreviousValue from 'lib/usePreviousValue';
5import usePreviousValue from 'lib/usePreviousValue';
6
6
7−// sync a known (i.e. mine) point to the cache (i.e display values)
7+const buildSyncHook = getFn =>
8−export function useSyncKnownPoints(points = []) {
8+ function SyncPoint(points = []) {
9− // TBD: right now we don't need anything more than the point itself
9+ const fn = getFn();
10− // but soon we'll need to know information around "key type"
10+ const prevPoints = usePreviousValue(points);
11−}
12
11
13−// sync a foreign (not the user's) point to the cache
12+ useEffect(() => {
14−export function useSyncForeignPoints(points = []) {
13+ // sync points that are new between renders
15− const { syncForeignPoint } = usePointCache();
14+ const newPoints = difference(points, prevPoints);
16− const prevPoints = usePreviousValue(points);
15+ Promise.all(newPoints.map(fn));
16+ }, [fn, prevPoints, points]);
17+ };
17
18
18− useEffect(() => {
19+export const useSyncKnownPoints = buildSyncHook(function() {
19− if (!isEqual(prevPoints, points)) {
20+ const { syncKnownPoint } = usePointCache();
20− // if the actual set of points changes, sync the whole array
21+ return syncKnownPoint;
21− // TODO: we should get clever about caching
22− points.forEach(point => syncForeignPoint(point));
23− }
24− }, [syncForeignPoint, prevPoints, points]);
25−}
22+});
26
23
27−// sync an owned point to the cache
24+export const useSyncForeignPoints = buildSyncHook(function() {
28−export function useSyncOwnedPoints(points = []) {
29− const { syncOwnedPoint } = usePointCache();
25+ const { syncForeignPoint } = usePointCache();
30− const prevPoints = usePreviousValue(points);
26+ return syncForeignPoint;
27+});
31
28
32− useEffect(() => {
29+export const useSyncOwnedPoints = buildSyncHook(function() {
33− if (!isEqual(prevPoints, points)) {
30+ const { syncOwnedPoint } = usePointCache();
34− // if the actual set of points changes, sync the whole array
35− // TODO: we should get clever about caching
36− points.forEach(point => syncOwnedPoint(point));
37− }
38− }, [syncOwnedPoint, prevPoints, points]);
31+ return syncOwnedPoint;
39−}
32+});
src/lib/validators.js+30−7
@@ -1,7 +1,9 @@
1import * as bip39 from 'bip39';
1import * as bip39 from 'bip39';
2import * as ob from 'urbit-ob';
2import * as ob from 'urbit-ob';
3+import { identity } from 'lodash';
3
4
4import { isValidAddress } from './wallet';
5import { isValidAddress } from './wallet';
6+import patp2dec from './patp2dec';
5
7
6// via: https://emailregex.com/
8// via: https://emailregex.com/
7const emailRegExp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
9const emailRegExp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@@ -10,19 +12,25 @@ const emailRegExp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))
10const hexRegExp = /[0-9A-Fa-f]{64}/g;
12const hexRegExp = /[0-9A-Fa-f]{64}/g;
11
13
12// Wraps single validation functions in a controlled and predictable way.
14// Wraps single validation functions in a controlled and predictable way.
13−export const simpleValidatorWrapper = config => {
15+export const simpleValidatorWrapper = ({
16+ prevMessage,
17+ transform = identity,
18+ validator,
19+ error,
20+}) => {
14 // If a previous validation has already failed, skip this validation and
21 // If a previous validation has already failed, skip this validation and
15 // return the prev message to the next stage in the validation function chain.
22 // return the prev message to the next stage in the validation function chain.
16 // Failed validations should drop all the way down the chain and drop out of
23 // Failed validations should drop all the way down the chain and drop out of
17 // the output.
24 // the output.
18− if (!config.prevMessage.pass) {
25+ if (!prevMessage.pass) {
19− return config.prevMessage;
26+ return prevMessage;
20 }
27 }
21
28
22 // Run the validator and return the result.
29 // Run the validator and return the result.
23− return config.validator(config.prevMessage.data)
30+ const pass = validator(prevMessage.data);
24− ? newMessage(config.prevMessage.data, true, null)
31+ const data = pass ? transform(prevMessage.data) : prevMessage.data;
25− : newMessage(config.prevMessage.data, false, config.error);
32+ console.log(pass, data);
33+ return newMessage(data, pass, !pass && error);
26};
34};
27
35
28// Validation message
36// Validation message
@@ -73,7 +81,7 @@ export const validateGalaxy = m =>
73 validator: d => {
81 validator: d => {
74 let point;
82 let point;
75 try {
83 try {
76− point = parseInt(ob.patp2dec(d), 10);
84+ point = patp2dec(d);
77 return point >= 0 && point < 256;
85 return point >= 0 && point < 256;
78 } catch (e) {
86 } catch (e) {
79 return false;
87 return false;
@@ -162,3 +170,18 @@ export const validateEmail = m =>
162 validator: d => emailRegExp.test(d),
170 validator: d => emailRegExp.test(d),
163 error: 'This is not a valid email address',
171 error: 'This is not a valid email address',
164 });
172 });
173+
174+export const validateInt = m =>
175+ simpleValidatorWrapper({
176+ prevMessage: m,
177+ transform: d => parseInt(d, 10),
178+ validator: d => {
179+ try {
180+ parseInt(d, 10);
181+ return;
182+ } catch {
183+ return false;
184+ }
185+ },
186+ error: 'This is not a valid number',
187+ });
src/store/lib/usePointStore.js+9−2
@@ -14,6 +14,12 @@ export default function usePointStore() {
14 ...controlledPoints
14 ...controlledPoints
15 } = useControlledPointsStore();
15 } = useControlledPointsStore();
16
16
17+ // sync all of the on-chain info required to display a known point
18+ const syncKnownPoint = useCallback(
19+ async point => Promise.all([syncBirthday(point)]),
20+ [syncBirthday]
21+ );
22+
17 // sync all of the on-chain info required to display a foreign point
23 // sync all of the on-chain info required to display a foreign point
18 const syncForeignPoint = useCallback(async point => Promise.all([]), []);
24 const syncForeignPoint = useCallback(async point => Promise.all([]), []);
19
25
@@ -21,11 +27,11 @@ export default function usePointStore() {
21 const syncOwnedPoint = useCallback(
27 const syncOwnedPoint = useCallback(
22 async point =>
28 async point =>
23 Promise.all([
29 Promise.all([
30+ syncKnownPoint(point),
24 syncDetails(point),
31 syncDetails(point),
25− syncBirthday(point),
26 syncInvites(point),
32 syncInvites(point),
27 ]),
33 ]),
28− [syncDetails, syncBirthday, syncInvites]
34+ [syncKnownPoint, syncDetails, syncInvites]
29 );
35 );
30
36
31 return {
37 return {
@@ -35,6 +41,7 @@ export default function usePointStore() {
35 ...controlledPoints,
41 ...controlledPoints,
36 syncInvites,
42 syncInvites,
37 syncControlledPoints,
43 syncControlledPoints,
44+ syncKnownPoint,
38 syncOwnedPoint,
45 syncOwnedPoint,
39 syncForeignPoint,
46 syncForeignPoint,
40 };
47 };
src/views/Activate.jsnew file+61
@@ -0,0 +1,61 @@
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';
7+
8+import View from 'components/View';
9+import Footer from 'components/Footer';
10+import { ForwardButton } from 'components/Buttons';
11+import Passport from 'components/Passport';
12+
13+import { useHistory } from 'store/history';
14+
15+import { ROUTE_NAMES } from 'lib/routeNames';
16+import { useSyncKnownPoints } from 'lib/useSyncPoints';
17+import patp2dec from 'lib/patp2dec';
18+
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();
33+
34+ useSyncKnownPoints([point.getOrElse(null)].filter(p => p !== null));
35+
36+ const ticketInput = useTicketInput({
37+ name: 'ticket',
38+ });
39+
40+ const goToLogin = useCallback(() => popAndPush(ROUTE_NAMES.LOGIN), [
41+ popAndPush,
42+ ]);
43+
44+ 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>
60+ );
61+}
src/views/CreateGalaxy.js+1−1
@@ -75,7 +75,7 @@ class CreateGalaxy extends React.Component {
75
75
76 const validContracts = need.contracts(props.contracts);
76 const validContracts = need.contracts(props.contracts);
77
77
78− const galaxyDec = parseInt(ob.patp2dec(state.galaxyName), 10);
78+ const galaxyDec = ob.patp2dec(state.galaxyName);
79
79
80 const txn = azimuth.ecliptic.createGalaxy(
80 const txn = azimuth.ecliptic.createGalaxy(
81 validContracts,
81 validContracts,
src/views/Landing.js+5−5
@@ -7,7 +7,7 @@ import { useHistory } from '../store/history';
7import View from 'components/View';
7import View from 'components/View';
8import { ForwardButton } from 'components/Buttons';
8import { ForwardButton } from 'components/Buttons';
9
9
10−function Landing(props) {
10+function Landing() {
11 const history = useHistory();
11 const history = useHistory();
12
12
13 return (
13 return (
@@ -22,13 +22,13 @@ function Landing(props) {
22 <ForwardButton
22 <ForwardButton
23 className="mt3"
23 className="mt3"
24 detail="If you were sent an Azimuth invite code."
24 detail="If you were sent an Azimuth invite code."
25− onClick={() => history.push(ROUTE_NAMES.INVITE_TICKET)}>
25+ onClick={() => history.push(ROUTE_NAMES.ACTIVATE)}>
26− Claim an invite
26+ Activate a Point
27 </ForwardButton>
27 </ForwardButton>
28
28
29 <ForwardButton
29 <ForwardButton
30 className="mt3"
30 className="mt3"
31− detail="Shiny new Login flow."
31+ detail="Login to an activated point."
32 onClick={() => history.push(ROUTE_NAMES.LOGIN)}>
32 onClick={() => history.push(ROUTE_NAMES.LOGIN)}>
33 Login
33 Login
34 </ForwardButton>
34 </ForwardButton>
@@ -37,7 +37,7 @@ function Landing(props) {
37 className="mt3"
37 className="mt3"
38 detail="View an Azimuth point without signing into a wallet."
38 detail="View an Azimuth point without signing into a wallet."
39 onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
39 onClick={() => history.push(ROUTE_NAMES.VIEW_POINT)}>
40− View a point
40+ View a Point
41 </ForwardButton>
41 </ForwardButton>
42 </View>
42 </View>
43 );
43 );
src/views/Login/Shards.js−1
@@ -23,7 +23,6 @@ export default function Shards({ loginCompleted }) {
23
23
24 const pointInput = usePointInput({
24 const pointInput = usePointInput({
25 name: 'point',
25 name: 'point',
26− label: 'Point',
27 autoFocus: true,
26 autoFocus: true,
28 });
27 });
29 const pointName = pointInput.data;
28 const pointName = pointInput.data;
src/views/Login/Ticket.js+1−3
@@ -37,16 +37,14 @@ export default function Ticket({ advanced, loginCompleted }) {
37 // inputs
37 // inputs
38 const pointInput = usePointInput({
38 const pointInput = usePointInput({
39 name: 'point',
39 name: 'point',
40− label: 'Point',
41 //TODO deduce point name from URL if we can, prefill input if we found it
40 //TODO deduce point name from URL if we can, prefill input if we found it
42− // initialValue: '~',
43 autoFocus: true,
41 autoFocus: true,
44 });
42 });
45 const pointName = pointInput.data;
43 const pointName = pointInput.data;
46
44
47 const ticketInput = useTicketInput({
45 const ticketInput = useTicketInput({
48 name: 'ticket',
46 name: 'ticket',
49− label: 'Master ticket',
47+ label: 'Master Ticket',
50 });
48 });
51 const ticket = ticketInput.data;
49 const ticket = ticketInput.data;
52
50
src/views/Point.js+1−1
@@ -41,7 +41,7 @@ export default function Point() {
41
41
42 return (
42 return (
43 <View>
43 <View>
44− <Passport point={point} />
44+ <Passport point={Maybe.Just(point)} />
45 <Grid className="pt2">
45 <Grid className="pt2">
46 <Grid.Item full>
46 <Grid.Item full>
47 <ForwardButton disabled>Admin</ForwardButton>
47 <ForwardButton disabled>Admin</ForwardButton>
src/views/ViewPoint.js+2−4
@@ -1,6 +1,5 @@
1import React, { useCallback } from 'react';
1import React, { useCallback } from 'react';
2import Maybe from 'folktale/maybe';
2import Maybe from 'folktale/maybe';
3−import * as ob from 'urbit-ob';
4import { Input } from 'indigo-react';
3import { Input } from 'indigo-react';
5
4
6import { H1, P } from '../components/old/Base';
5import { H1, P } from '../components/old/Base';
@@ -9,6 +8,7 @@ import { useHistory } from 'store/history';
9import { usePointCursor } from 'store/pointCursor';
8import { usePointCursor } from 'store/pointCursor';
10
9
11import { ROUTE_NAMES } from 'lib/routeNames';
10import { ROUTE_NAMES } from 'lib/routeNames';
11+import patp2dec from 'lib/patp2dec';
12
12
13import View from 'components/View';
13import View from 'components/View';
14import { ForwardButton } from 'components/Buttons';
14import { ForwardButton } from 'components/Buttons';
@@ -19,15 +19,13 @@ export default function ViewPoint() {
19 const { setPointCursor } = usePointCursor();
19 const { setPointCursor } = usePointCursor();
20 const pointInput = usePointInput({
20 const pointInput = usePointInput({
21 name: 'point',
21 name: 'point',
22− label: 'Point Name',
23 autoFocus: true,
22 autoFocus: true,
24 });
23 });
25 const { data: pointName, pass } = pointInput;
24 const { data: pointName, pass } = pointInput;
26
25
27 const disabled = !pass;
26 const disabled = !pass;
28 const goForward = useCallback(() => {
27 const goForward = useCallback(() => {
29− setPointCursor(Maybe.Just(parseInt(ob.patp2dec(pointName), 10)));
28+ setPointCursor(Maybe.Just(patp2dec(pointName)));
30− // ^ pointCursor expects native number type, not string
31 history.popAndPush(ROUTE_NAMES.POINT);
29 history.popAndPush(ROUTE_NAMES.POINT);
32 }, [setPointCursor, history, pointName]);
30 }, [setPointCursor, history, pointName]);
33
31