on 0791f78 help sign in
bridge/src/views/Activate/ActivateCode.js
206 lines 6.3 kB history raw
1import React, { useCallback, useMemo, useRef } from 'react';
2import { Just } from 'folktale/maybe';
3import * as azimuth from 'azimuth-js';
4import { Grid, H4 } from 'indigo-react';
5import { FORM_ERROR } from 'final-form';
7import View from 'components/View';
8import { ForwardButton } from 'components/Buttons';
9import Passport from 'components/Passport';
10import WarningBox from 'components/WarningBox';
11import FooterButton from 'components/FooterButton';
13import { useNetwork } from 'store/network';
14import { useHistory } from 'store/history';
16import * as need from 'lib/need';
17import { ROUTE_NAMES } from 'lib/routeNames';
18import { DEFAULT_HD_PATH, walletFromMnemonic } from 'lib/wallet';
19import { generateWallet } from 'lib/invite';
20import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
21import { useLocalRouter } from 'lib/LocalRouter';
22import useImpliedTicket from 'lib/useImpliedTicket';
23import timeout from 'lib/timeout';
24import useHasDisclaimed from 'lib/useHasDisclaimed';
25import useBreakpoints from 'lib/useBreakpoints';
27import BridgeForm from 'form/BridgeForm';
28import SubmitButton from 'form/SubmitButton';
29import { TicketInput } from 'form/Inputs';
30import {
31 composeValidator,
32 buildPatqValidator,
33 hasErrors,
34} from 'form/validators';
35import FormError from 'form/FormError';
37import { useActivateFlow } from './ActivateFlow';
38import { hasWarnings } from 'form/helpers';
40export default function ActivateCode() {
41 const history = useHistory();
42 const { names, push } = useLocalRouter();
43 const { contracts } = useNetwork();
44 const impliedTicket = useImpliedTicket();
45 const [hasDisclaimed] = useHasDisclaimed();
46 const warnings = useRef({});
48 const {
49 setDerivedWallet,
50 setInviteWallet,
51 derivedPoint,
52 setDerivedPoint,
53 } = useActivateFlow();
54 // this is a pretty naive way to detect if we're on a mobile device
55 // (i.e. we're checking the width of the screen)
56 // but it will suffice for the 99% case and if someone wants to get around it
57 // well by golly they're allowed to turn their phone into landscape mode
58 // for this screen
59 const activationAllowed = useBreakpoints([false, true, true]);
61 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
62 history,
63 ]);
65 const goToPassport = useCallback(() => {
66 push(names.PASSPORT);
68 if (!hasDisclaimed) {
69 push(names.DISCLAIMER);
70 }
71 }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
73 const validateForm = useCallback((values, errors) => {
74 warnings.current.ticket = null;
76 if (hasErrors(errors)) {
77 return errors;
78 }
79 }, []);
81 const validate = useMemo(
82 () => composeValidator({ ticket: buildPatqValidator() }, validateForm),
83 [validateForm]
84 );
86 // set our state on submission
87 const onSubmit = useCallback(
88 async values => {
89 await timeout(16); // allow the ui changes to flush before we lag it out
91 const _contracts = need.contracts(contracts);
92 const { seed } = await generateTemporaryOwnershipWallet(values.ticket);
94 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
96 const _inviteWallet = need.wallet(inviteWallet);
98 const owned = await azimuth.azimuth.getOwnedPoints(
99 _contracts,
100 _inviteWallet.address
101 );
102 const transferring = await azimuth.azimuth.getTransferringFor(
103 _contracts,
104 _inviteWallet.address
105 );
106 const incoming = [...owned, ...transferring];
108 if (incoming.length > 0) {
109 if (incoming.length > 1) {
110 warnings.current.ticket =
111 'This invite code has multiple points available.\n' +
112 "Once you've activated this point, activate the next with the same process.";
113 }
115 const point = parseInt(incoming[0], 10);
117 setDerivedPoint(Just(point));
118 setInviteWallet(inviteWallet);
119 setDerivedWallet(Just(await generateWallet(point)));
120 } else {
121 return {
122 [FORM_ERROR]:
123 'Invite code has no claimable point.\n' +
124 'Check your invite code and try again?',
125 };
126 }
127 },
128 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
129 );
131 const afterSubmit = useCallback(() => {
132 if (hasWarnings(warnings.current)) {
133 return;
134 }
136 goToPassport();
137 }, [goToPassport]);
139 const initialValues = useMemo(() => ({ ticket: impliedTicket || '' }), [
140 impliedTicket,
141 ]);
143 return (
144 <View inset>
145 <Grid>
146 <Grid.Item full as={Passport} point={derivedPoint} />
147 <Grid.Item full as={H4} className="mt3 mb2">
148 Activate
149 </Grid.Item>
150 <BridgeForm
151 validate={validate}
152 onSubmit={onSubmit}
153 afterSubmit={afterSubmit}
154 initialValues={initialValues}>
155 {({ validating, submitting, submitSucceeded, handleSubmit }) => (
156 <>
157 <Grid.Item
158 full
159 as={TicketInput}
160 name="ticket"
161 label="Activation Code"
162 disabled={!activationAllowed}
163 warning={warnings.current.ticket}
164 />
166 <Grid.Item full as={FormError} />
168 {submitSucceeded ? (
169 <Grid.Item
170 full
171 as={ForwardButton}
172 solid
173 className="mt4"
174 onClick={goToPassport}>
175 Continue Activation
176 </Grid.Item>
177 ) : (
178 <Grid.Item
179 full
180 as={SubmitButton}
181 className="mt4"
182 handleSubmit={handleSubmit}>
183 {validating
184 ? 'Deriving...'
185 : submitting
186 ? 'Generating...'
187 : 'Go'}
188 </Grid.Item>
189 )}
191 {!activationAllowed && (
192 <Grid.Item full as={WarningBox} className="mt4">
193 For your security, please access Bridge on a desktop device.
194 </Grid.Item>
195 )}
196 </>
197 )}
198 </BridgeForm>
199 </Grid>
201 <FooterButton as={ForwardButton} onClick={goToLogin}>
202 Login
203 </FooterButton>
204 </View>
205 );