on master help sign in
checkpoint: using why-did-you-render
e273c1cMatt Condon (shrugs)
package-lock.json+42
@@ -6918,6 +6918,19 @@
6918 }
6918 }
6919 }
6919 }
6920 },
6920 },
6921+ "final-form": {
6922+ "version": "4.18.2",
6923+ "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.18.2.tgz",
6924+ "integrity": "sha512-VQx/5x9M4CiC8fG678Dm1IS3mXvBl7ZNIUx5tUZCk00lFImJzQix4KO0+eGtl49sha2bYOxuYn8jRJiq6sazXA==",
6925+ "requires": {
6926+ "@babel/runtime": "^7.3.1"
6927+ }
6928+ },
6929+ "final-form-set-field-data": {
6930+ "version": "1.0.2",
6931+ "resolved": "https://registry.npmjs.org/final-form-set-field-data/-/final-form-set-field-data-1.0.2.tgz",
6932+ "integrity": "sha512-gAnENimyQ5GW3OEGca5pbwm4lYshW2orzfBlPUYqzcm7ZxkQrVO8FqCAgEcCM+Rq9U1OU0q+D+UkqETvvDY6jw=="
6933+ },
6921 "finalhandler": {
6934 "finalhandler": {
6922 "version": "1.1.1",
6935 "version": "1.1.1",
6923 "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
6936 "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
@@ -14511,6 +14524,25 @@
14511 "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz",
14524 "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.6.tgz",
14512 "integrity": "sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q=="
14525 "integrity": "sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q=="
14513 },
14526 },
14527+ "react-final-form": {
14528+ "version": "6.3.0",
14529+ "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.3.0.tgz",
14530+ "integrity": "sha512-jijhXR1fFGUBQwNOSqF4MK8XJO7Ynl1p8vcFsnQS0INSkGI52+4IagjUgtHj3w8EviIHPFK/Eflji6FELUl07w==",
14531+ "requires": {
14532+ "@babel/runtime": "^7.4.5",
14533+ "ts-essentials": "^2.0.8"
14534+ },
14535+ "dependencies": {
14536+ "@babel/runtime": {
14537+ "version": "7.5.5",
14538+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz",
14539+ "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==",
14540+ "requires": {
14541+ "regenerator-runtime": "^0.13.2"
14542+ }
14543+ }
14544+ }
14545+ },
14514 "react-hot-loader": {
14546 "react-hot-loader": {
14515 "version": "4.12.0",
14547 "version": "4.12.0",
14516 "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.0.tgz",
14548 "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.0.tgz",
@@ -14721,6 +14753,11 @@
14721 "regenerate": "^1.4.0"
14753 "regenerate": "^1.4.0"
14722 }
14754 }
14723 },
14755 },
14756+ "regenerator-runtime": {
14757+ "version": "0.13.3",
14758+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
14759+ "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
14760+ },
14724 "regenerator-transform": {
14761 "regenerator-transform": {
14725 "version": "0.14.0",
14762 "version": "0.14.0",
14726 "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz",
14763 "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz",
@@ -17199,6 +17236,11 @@
17199 "solc": "0.4.24"
17236 "solc": "0.4.24"
17200 }
17237 }
17201 },
17238 },
17239+ "ts-essentials": {
17240+ "version": "2.0.12",
17241+ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz",
17242+ "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w=="
17243+ },
17202 "ts-pnp": {
17244 "ts-pnp": {
17203 "version": "1.1.2",
17245 "version": "1.1.2",
17204 "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz",
17246 "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.2.tgz",
package.json+3
@@ -28,6 +28,8 @@
28 "ethereum-blockies-base64": "^1.0.2",
28 "ethereum-blockies-base64": "^1.0.2",
29 "ethereumjs-tx": "^1.3.7",
29 "ethereumjs-tx": "^1.3.7",
30 "file-saver": "^2.0.0",
30 "file-saver": "^2.0.0",
31+ "final-form": "^4.18.2",
32+ "final-form-set-field-data": "^1.0.2",
31 "folktale": "^2.3.1",
33 "folktale": "^2.3.1",
32 "jszip": "^3.1.5",
34 "jszip": "^3.1.5",
33 "keccak": "^1.4.0",
35 "keccak": "^1.4.0",
@@ -38,6 +40,7 @@
38 "react": "^16.8.6",
40 "react": "^16.8.6",
39 "react-app-rewire-hot-loader": "^2.0.1",
41 "react-app-rewire-hot-loader": "^2.0.1",
40 "react-dom": "^16.8.6",
42 "react-dom": "^16.8.6",
43+ "react-final-form": "^6.3.0",
41 "react-hot-loader": "^4.12.0",
44 "react-hot-loader": "^4.12.0",
42 "react-scripts": "3.0.1",
45 "react-scripts": "3.0.1",
43 "react-teleporter": "^1.1.0",
46 "react-teleporter": "^1.1.0",
src/Bridge.js+1−5
@@ -41,11 +41,7 @@ function useInitialRoutes() {
41 const hasImpliedTicket = !!useImpliedTicket();
41 const hasImpliedTicket = !!useImpliedTicket();
42
42
43 if (IS_STUBBED) {
43 if (IS_STUBBED) {
44− return [
45− { key: ROUTE_NAMES.LOGIN },
44+ return [{ key: ROUTE_NAMES.ACTIVATE }];
46− { key: ROUTE_NAMES.POINTS },
47− { key: ROUTE_NAMES.POINT },
48− ];
49 }
45 }
50
46
51 return hasImpliedTicket
47 return hasImpliedTicket
src/form/Autosaver.jsnew file+20
@@ -0,0 +1,20 @@
1+import { useEffect } from 'react';
2+import { useFormState } from 'react-final-form';
3+
4+export default function Autosaver({ onValues }) {
5+ const { valid, validating, values } = useFormState({
6+ subscription: {
7+ valid: true,
8+ validating: true,
9+ values: true,
10+ },
11+ });
12+
13+ useEffect(() => {
14+ if (valid && !validating) {
15+ onValues && onValues(values);
16+ }
17+ }, [onValues, valid, validating, values]);
18+
19+ return null;
20+}
src/form/BridgeForm.jsnew file+20
@@ -0,0 +1,20 @@
1+import React from 'react';
2+import { Form } from 'react-final-form';
3+import setFieldData from 'final-form-set-field-data';
4+
5+import FormError from './FormError';
6+import Autosaver from './Autosaver';
7+
8+export default function BridgeForm({ children, onValues, ...rest }) {
9+ return (
10+ <Form mutators={{ setFieldData }} {...rest}>
11+ {formProps => (
12+ <>
13+ {children(formProps)}
14+ {onValues && <Autosaver onValues={onValues} />}
15+ <FormError />
16+ </>
17+ )}
18+ </Form>
19+ );
20+}
src/form/FormError.jsnew file+9
@@ -0,0 +1,9 @@
1+import React from 'react';
2+import { useFormState } from 'react-final-form';
3+import { ErrorText } from 'indigo-react';
4+
5+export default function FormError() {
6+ const { submitError } = useFormState({ subscription: { submitError: true } });
7+
8+ return submitError ? <ErrorText>{submitError}</ErrorText> : null;
9+}
src/form/Inputs.jsnew file+56
@@ -0,0 +1,56 @@
1+import React, { useMemo } from 'react';
2+import { Input, AccessoryIcon } from 'indigo-react';
3+import { useField } from 'react-final-form';
4+import {
5+ validateNotEmpty,
6+ validateTicket,
7+ kDefaultValidator,
8+} from 'lib/validators';
9+import { compose } from 'lib/lib';
10+
11+const PLACEHOLDER_TICKET = '~sampel-ticlyt-migfun-falmel';
12+
13+const buildValidator = (
14+ validators = [],
15+ fn = x => undefined
16+) => async value => {
17+ console.log('validating', value);
18+ return (
19+ compose(
20+ ...validators,
21+ kDefaultValidator
22+ )(value).error || (await fn(value))
23+ );
24+};
25+
26+const kTicketValidators = [validateTicket, validateNotEmpty];
27+export function TicketInput({ name, validators = [], config = {}, ...rest }) {
28+ const { valid, error, validating } = useField(name, {
29+ subscription: { error: true, validating: true },
30+ });
31+
32+ const validate = useMemo(
33+ () =>
34+ buildValidator([...validators, ...kTicketValidators], config.validate),
35+ [config.validate, validators]
36+ );
37+
38+ return (
39+ <Input
40+ name={name}
41+ placeholder={PLACEHOLDER_TICKET}
42+ accessory={
43+ error ? (
44+ <AccessoryIcon.Failure />
45+ ) : validating ? (
46+ <AccessoryIcon.Pending />
47+ ) : valid ? (
48+ <AccessoryIcon.Success />
49+ ) : null
50+ }
51+ config={{ validate }}
52+ mono
53+ {...rest}
54+ />
55+ );
56+}
src/form/SubmitButton.jsnew file+35
@@ -0,0 +1,35 @@
1+import React from 'react';
2+import cn from 'classnames';
3+
4+import { ForwardButton } from 'components/Buttons';
5+
6+import { blinkIf } from 'components/Blinky';
7+import { useFormState } from 'react-final-form';
8+
9+export default function SubmitButton({
10+ as: As = ForwardButton,
11+ className,
12+ children,
13+ handleSubmit,
14+ ...rest
15+}) {
16+ const { valid, validating, submitting } = useFormState({
17+ subscription: {
18+ valid: true,
19+ validating: true,
20+ submitting: true,
21+ },
22+ });
23+
24+ return (
25+ <As
26+ className={cn('mt4', className)}
27+ disabled={!valid || validating || submitting}
28+ accessory={blinkIf(validating || submitting)}
29+ onClick={handleSubmit}
30+ solid
31+ {...rest}>
32+ {children}
33+ </As>
34+ );
35+}
src/indigo-react/components/Input.js+37−35
@@ -3,50 +3,53 @@ import cn from 'classnames';
3
3
4import Flex from './Flex';
4import Flex from './Flex';
5import { ErrorText } from './Typography';
5import { ErrorText } from './Typography';
6+import { useField } from 'react-final-form';
6
7
7−export default React.memo(function Input({
8+export default function Input({
8 // visuals
9 // visuals
9 type,
10 type,
10 name,
11 name,
11 label,
12 label,
12 className,
13 className,
13 accessory,
14 accessory,
15+ disabled = false,
14 mono = false,
16 mono = false,
15
17
16 // callbacks
18 // callbacks
17 onEnter,
19 onEnter,
18
20
19− // state from hook
21+ // state
20− focused,
22+ config,
21− pass,
22− syncPass,
23− visiblyPassed,
24− error,
25− hintError,
26− data,
27− bind,
28− autoFocus,
29− disabled,
30− touched,
31−
32− // ignored
33− initialValue,
34− validators,
35− transformers,
36
23
37 // extra
24 // extra
38− textarea = false,
39 ...rest
25 ...rest
40}) {
26}) {
41− // NB(shrugs): we disable exhaustive deps because we don't want the callbacks
27+ const {
42− // (whose identity might change between renders) to trigger a re-notify of
28+ input,
43− // state that we already know
29+ meta: {
44− // this also happens to prevent render loops when rendering a map of inputs
30+ active,
31+ data,
32+ dirty,
33+ error,
34+ modified,
35+ pristine,
36+ submitError,
37+ submitFailed,
38+ submitSucceeded,
39+ submitting,
40+ touched,
41+ valid,
42+ validating,
43+ visited,
44+ },
45+ } = useField(name, config);
46+
47+ disabled = disabled || submitting;
45
48
46 // notify parent of enter keypress iff not disabled and passing
49 // notify parent of enter keypress iff not disabled and passing
47 const onKeyPress = useCallback(
50 const onKeyPress = useCallback(
48− e => !disabled && pass && e.key === 'Enter' && onEnter && onEnter(),
51+ e => !disabled && valid && e.key === 'Enter' && onEnter && onEnter(),
49− [disabled, pass] // eslint-disable-line react-hooks/exhaustive-deps
52+ [disabled, valid] // eslint-disable-line react-hooks/exhaustive-deps
50 );
53 );
51
54
52 return (
55 return (
@@ -83,21 +86,20 @@ export default React.memo(function Input({
83 'bg-gray1': disabled,
86 'bg-gray1': disabled,
84 },
87 },
85 {
88 {
86− gray4: !focused && !touched,
89+ gray4: !active && !touched,
87− black: focused || touched,
90+ black: active || touched,
88 },
91 },
89 {
92 {
90− 'b-green3': visiblyPassed,
93+ 'b-green3': valid,
91− 'b-black': focused && !visiblyPassed,
94+ 'b-black': active && !valid,
92− 'b-yellow3': !focused && hintError,
95+ 'b-yellow3': !active && touched && error,
93− 'b-gray2': !focused && !hintError && !visiblyPassed,
96+ 'b-gray2': !active && !error && !valid,
94 }
97 }
95− // TODO: inputClassName ?
96 )}
98 )}
97 id={name}
99 id={name}
98 name={name}
100 name={name}
99 onKeyPress={onKeyPress}
101 onKeyPress={onKeyPress}
100− {...bind}
102+ {...input}
101 flex
103 flex
102 />
104 />
103 {accessory && (
105 {accessory && (
@@ -114,11 +116,11 @@ export default React.memo(function Input({
114 </div>
116 </div>
115 )}
117 )}
116 </Flex.Item>
118 </Flex.Item>
117− {error && (
119+ {touched && error && (
118 <Flex.Item as={ErrorText} className="mv1">
120 <Flex.Item as={ErrorText} className="mv1">
119 {error}
121 {error}
120 </Flex.Item>
122 </Flex.Item>
121 )}
123 )}
122 </Flex>
124 </Flex>
123 );
125 );
124−});
126+}
src/views/Activate.js+7−1
@@ -1,11 +1,12 @@
1import React from 'react';
1import React from 'react';
2
2
3import useRouter from 'lib/useRouter';
3import useRouter from 'lib/useRouter';
4+import { LocalRouterProvider } from 'lib/LocalRouter';
5+import { useSyncKnownPoints } from 'lib/useSyncPoints';
4
6
5import { ActivateFlowProvider } from './Activate/ActivateFlow';
7import { ActivateFlowProvider } from './Activate/ActivateFlow';
6import useActivateFlowState from './Activate/useActivateFlowState';
8import useActivateFlowState from './Activate/useActivateFlowState';
7import ActivateCode from './Activate/ActivateCode';
9import ActivateCode from './Activate/ActivateCode';
8−import { LocalRouterProvider } from 'lib/LocalRouter';
9import ActivatePassport from './Activate/ActivatePassport';
10import ActivatePassport from './Activate/ActivatePassport';
10import Disclaimer from './Disclaimer';
11import Disclaimer from './Disclaimer';
11
12
@@ -29,6 +30,11 @@ export default function Activate() {
29 initialRoutes: [{ key: NAMES.CODE }],
30 initialRoutes: [{ key: NAMES.CODE }],
30 });
31 });
31
32
33+ // when we know the derived point, ensure we have the data to display it
34+ useSyncKnownPoints(
35+ [state.derivedPoint.getOrElse(null)].filter(p => p !== null)
36+ );
37+
32 return (
38 return (
33 <LocalRouterProvider value={router}>
39 <LocalRouterProvider value={router}>
34 <ActivateFlowProvider value={state}>
40 <ActivateFlowProvider value={state}>
src/views/Activate/ActivateCode.js+89−96
@@ -1,7 +1,7 @@
1−import React, { useCallback, useEffect, useState } from 'react';
1+import React, { useCallback, useRef } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import * as azimuth from 'azimuth-js';
3import * as azimuth from 'azimuth-js';
4−import { Grid, Input, H4, ErrorText } from 'indigo-react';
4+import { Grid, H4 } from 'indigo-react';
5
5
6import View from 'components/View';
6import View from 'components/View';
7import { ForwardButton } from 'components/Buttons';
7import { ForwardButton } from 'components/Buttons';
@@ -9,12 +9,9 @@ import Passport from 'components/Passport';
9
9
10import { useHistory } from 'store/history';
10import { useHistory } from 'store/history';
11
11
12−import { useTicketInput } from 'lib/useInputs';
13import * as need from 'lib/need';
12import * as need from 'lib/need';
14import { ROUTE_NAMES } from 'lib/routeNames';
13import { ROUTE_NAMES } from 'lib/routeNames';
15−import { useSyncKnownPoints } from 'lib/useSyncPoints';
16import FooterButton from 'components/FooterButton';
14import FooterButton from 'components/FooterButton';
17−import { blinkIf } from 'components/Blinky';
18import { DEFAULT_HD_PATH, walletFromMnemonic } from 'lib/wallet';
15import { DEFAULT_HD_PATH, walletFromMnemonic } from 'lib/wallet';
19import { useNetwork } from 'store/network';
16import { useNetwork } from 'store/network';
20import { generateWallet } from 'lib/invite';
17import { generateWallet } from 'lib/invite';
@@ -22,111 +19,96 @@ import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
22import { useActivateFlow } from './ActivateFlow';
19import { useActivateFlow } from './ActivateFlow';
23import { useLocalRouter } from 'lib/LocalRouter';
20import { useLocalRouter } from 'lib/LocalRouter';
24import useImpliedTicket from 'lib/useImpliedTicket';
21import useImpliedTicket from 'lib/useImpliedTicket';
22+import timeout from 'lib/timeout';
25import useHasDisclaimed from 'lib/useHasDisclaimed';
23import useHasDisclaimed from 'lib/useHasDisclaimed';
26
24
25+import BridgeForm from 'form/BridgeForm';
26+import SubmitButton from 'form/SubmitButton';
27+import { TicketInput } from 'form/Inputs';
28+
27export default function ActivateCode() {
29export default function ActivateCode() {
28 const history = useHistory();
30 const history = useHistory();
29 const { names, push } = useLocalRouter();
31 const { names, push } = useLocalRouter();
30 const { contracts } = useNetwork();
32 const { contracts } = useNetwork();
31 const impliedTicket = useImpliedTicket();
33 const impliedTicket = useImpliedTicket();
32 const [hasDisclaimed] = useHasDisclaimed();
34 const [hasDisclaimed] = useHasDisclaimed();
33− const [generalError, setGeneralError] = useState();
35+
34− const [deriving, setDeriving] = useState(false);
36+ const cachedInviteWallet = useRef(Nothing());
37+ const cachedPoint = useRef();
38+
35 const {
39 const {
36− derivedWallet,
37 setDerivedWallet,
40 setDerivedWallet,
38 setInviteWallet,
41 setInviteWallet,
39 derivedPoint,
42 derivedPoint,
40 setDerivedPoint,
43 setDerivedPoint,
41 } = useActivateFlow();
44 } = useActivateFlow();
42
45
43− const [ticketInput, { pass: validTicket, data: ticket }] = useTicketInput({
44− name: 'ticket',
45− label: 'Activation Code',
46− initialValue: impliedTicket || '',
47− autoFocus: true,
48− });
49−
50 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
46 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
51 history,
47 history,
52 ]);
48 ]);
53 const goToPassport = useCallback(() => {
49 const goToPassport = useCallback(() => {
54 push(names.PASSPORT);
50 push(names.PASSPORT);
51+
55 if (!hasDisclaimed) {
52 if (!hasDisclaimed) {
56 push(names.DISCLAIMER);
53 push(names.DISCLAIMER);
57 }
54 }
58 }, [names, push, hasDisclaimed]);
55 }, [names, push, hasDisclaimed]);
59
56
60− const pass = derivedWallet.matchWith({
57+ const validate = useCallback(
61− Nothing: () => false,
58+ async ticket => {
62− Just: () => true,
59+ await timeout(100); // allow the ui changes to flush before we lag it out
63− });
64
60
65− useEffect(() => {
66− if (validTicket) {
67 const _contracts = need.contracts(contracts);
61 const _contracts = need.contracts(contracts);
62+ const { seed } = await generateTemporaryOwnershipWallet(ticket);
63+
64+ // TODO(fang): isn't all this accessible in the ownership object?
65+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
66+ cachedInviteWallet.current = inviteWallet;
67+
68+ const _inviteWallet = need.wallet(inviteWallet);
69+
70+ const owned = await azimuth.azimuth.getOwnedPoints(
71+ _contracts,
72+ _inviteWallet.address
73+ );
74+ const transferring = await azimuth.azimuth.getTransferringFor(
75+ _contracts,
76+ _inviteWallet.address
77+ );
78+ const incoming = [...owned, ...transferring];
79+
80+ if (incoming.length > 0) {
81+ if (incoming.length > 1) {
82+ // TODO: warnings
83+ // setGeneralError(
84+ // 'This invite code has multiple points available.\n' +
85+ // "Once you've activated this point, activate the next with the same process."
86+ // );
87+ }
68
88
69− setDeriving(true);
89+ const point = parseInt(incoming[0], 10);
70− // when the ticket becomes valid, derive the point
90+ // setDerivedPoint(Just(point));
71− (async () => {
91+ cachedPoint.current = point;
72− const { seed } = await generateTemporaryOwnershipWallet(ticket);
92+ } else {
73−
93+ return (
74− //TODO isn't all this accessible in the ownership object?
94+ 'Invite code has no claimable point.\n' +
75− const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
95+ 'Check your invite code and try again?'
76− const _inviteWallet = need.wallet(inviteWallet);
77−
78− const owned = await azimuth.azimuth.getOwnedPoints(
79− _contracts,
80− _inviteWallet.address
81− );
82− const transferring = await azimuth.azimuth.getTransferringFor(
83− _contracts,
84− _inviteWallet.address
85 );
96 );
86− const incoming = [...owned, ...transferring];
97+ }
87−
98+ },
88− let realPoint = Nothing();
99+ [contracts]
89− let wallet = Nothing();
90−
91− if (incoming.length > 0) {
92− const pointNum = parseInt(incoming[0], 10);
93− realPoint = Just(pointNum);
94− wallet = Just(await generateWallet(pointNum));
95−
96− if (incoming.length > 1) {
97− setGeneralError(
98− 'This invite code has multiple points available.\n' +
99− "Once you've activated this point, activate the next with the same process."
100− );
100+ );
101− } else {
102− setGeneralError(false);
103− }
104− } else {
105− setGeneralError(
106− 'Invite code has no claimable point.\n' +
107− 'Check your invite code and try again?'
108− );
109− }
110
101
111− setDerivedPoint(realPoint);
102+ const onSubmit = useCallback(
112− setDerivedWallet(wallet);
103+ async values => {
113− setInviteWallet(inviteWallet);
104+ setInviteWallet(cachedInviteWallet.current);
114− setDeriving(false);
115− })();
116− } else {
117− setGeneralError(false);
118− }
119− }, [
120− validTicket,
121− contracts,
122− ticket,
123− setDerivedPoint,
105+ setDerivedPoint(Just(cachedPoint.current));
124− setDerivedWallet,
106+ setDerivedWallet(Just(await generateWallet(cachedPoint)));
125− setInviteWallet,
126− ]);
127
107
128− // when we know the derived point, ensure we have the data to display it
108+ goToPassport();
129− useSyncKnownPoints([derivedPoint.getOrElse(null)].filter(p => p !== null));
109+ },
110+ [goToPassport, setDerivedPoint, setDerivedWallet, setInviteWallet]
111+ );
130
112
131 return (
113 return (
132 <View inset>
114 <View inset>
@@ -135,23 +117,34 @@ export default function ActivateCode() {
135 <Grid.Item as={H4} className="mt3 mb2" full>
117 <Grid.Item as={H4} className="mt3 mb2" full>
136 Activate
118 Activate
137 </Grid.Item>
119 </Grid.Item>
120+ <BridgeForm
121+ onSubmit={onSubmit}
122+ initialValues={{ ticket: impliedTicket || '' }}>
123+ {({ validating, submitting, handleSubmit }) => (
124+ <>
138− <Grid.Item as={Input} {...ticketInput} full />
125+ <Grid.Item
139− {generalError && (
126+ full
127+ as={TicketInput}
128+ name="ticket"
129+ label="Activation Code"
130+ config={{ validate }}
131+ autoFocus
132+ />
133+
140− <Grid.Item full>
134+ <Grid.Item
141− <ErrorText>{generalError}</ErrorText>{' '}
135+ full
142− </Grid.Item>
136+ as={SubmitButton}
143− )}
144− <Grid.Item
145− as={ForwardButton}
146− className="mt4"
137+ className="mt4"
147− disabled={!pass || deriving}
138+ handleSubmit={handleSubmit}>
148− accessory={blinkIf(deriving)}
139+ {validating
149− onClick={goToPassport}
150− solid
151− full>
152− {deriving && 'Deriving...'}
140+ ? 'Deriving...'
153− {!deriving && 'Go'}
141+ : submitting
142+ ? 'Generating...'
143+ : 'Go'}
154− </Grid.Item>
144+ </Grid.Item>
145+ </>
146+ )}
147+ </BridgeForm>
155 </Grid>
148 </Grid>
156 <FooterButton as={ForwardButton} onClick={goToLogin}>
149 <FooterButton as={ForwardButton} onClick={goToLogin}>
157 Login
150 Login