on cfae92f help sign in
bridge/src/views/Activate/ActivateCode.js
208 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, CheckboxInput } 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 buildCheckboxValidator,
35} from 'form/validators';
36import FormError from 'form/FormError';
38import { useActivateFlow } from './ActivateFlow';
39import { WARNING } from 'form/helpers';
41export default function ActivateCode() {
42 const history = useHistory();
43 const { names, push } = useLocalRouter();
44 const { contracts } = useNetwork();
45 const impliedTicket = useImpliedTicket();
46 const [hasDisclaimed] = useHasDisclaimed();
47 const didWarn = useRef(false);
49 const {
50 setDerivedWallet,
51 setInviteWallet,
52 derivedPoint,
53 setDerivedPoint,
54 } = useActivateFlow();
55 // this is a pretty naive way to detect if we're on a mobile device
56 // (i.e. we're checking the width of the screen)
57 // but it will suffice for the 99% case and if someone wants to get around it
58 // well by golly they're allowed to turn their phone into landscape mode
59 // for this screen
60 const activationAllowed = useBreakpoints([false, true, true]);
62 const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
63 history,
64 ]);
66 const goToPassport = useCallback(() => {
67 push(names.PASSPORT);
69 if (!hasDisclaimed) {
70 push(names.DISCLAIMER);
71 }
72 }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
74 const validateForm = useCallback((values, errors) => {
75 didWarn.current = false;
77 if (hasErrors(errors)) {
78 return errors;
79 }
80 }, []);
82 const validate = useMemo(
83 () =>
84 composeValidator(
85 { ticket: buildPatqValidator(), showTicket: buildCheckboxValidator() },
86 validateForm
87 ),
88 [validateForm]
89 );
91 // set our state on submission
92 const onSubmit = useCallback(
93 async values => {
94 await timeout(16); // allow the ui changes to flush before we lag it out
96 const _contracts = need.contracts(contracts);
97 const { seed } = await generateTemporaryOwnershipWallet(values.ticket);
99 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
101 const _inviteWallet = need.wallet(inviteWallet);
103 const owned = await azimuth.azimuth.getOwnedPoints(
104 _contracts,
105 _inviteWallet.address
106 );
107 const transferring = await azimuth.azimuth.getTransferringFor(
108 _contracts,
109 _inviteWallet.address
110 );
111 const incoming = [...owned, ...transferring];
113 if (incoming.length > 0) {
114 if (incoming.length > 1 && !didWarn.current) {
115 didWarn.current = true;
116 return {
117 [WARNING]:
118 'This invite code has multiple points available. ' +
119 "Once you've activated this point, " +
120 'activate the next with the same process.',
121 };
122 }
124 const point = parseInt(incoming[0], 10);
126 setDerivedPoint(Just(point));
127 setInviteWallet(inviteWallet);
128 setDerivedWallet(Just(await generateWallet(point)));
129 } else {
130 return {
131 [FORM_ERROR]:
132 'Invite code has no claimable point.\n' +
133 'Check your invite code and try again?',
134 };
135 }
136 },
137 [contracts, setDerivedPoint, setDerivedWallet, setInviteWallet]
138 );
140 const initialValues = useMemo(
141 () => ({ ticket: impliedTicket || '', showTicket: true }),
142 [impliedTicket]
143 );
145 return (
146 <View inset>
147 <Grid>
148 <Grid.Item full as={Passport} point={derivedPoint} />
149 <Grid.Item full as={H4} className="mt3 mb2">
150 Activate
151 </Grid.Item>
152 <BridgeForm
153 validate={validate}
154 onSubmit={onSubmit}
155 afterSubmit={goToPassport}
156 initialValues={initialValues}>
157 {({ validating, values, submitting, handleSubmit }) => (
158 <>
159 <Grid.Item
160 full
161 as={TicketInput}
162 type={values.showTicket ? 'text' : 'password'}
163 name="ticket"
164 label="Activation Code"
165 disabled={!activationAllowed}
166 />
168 <Grid.Item
169 full
170 as={CheckboxInput}
171 name="showTicket"
172 label="Show"
173 />
175 <Grid.Item full as={FormError} />
177 <Grid.Item
178 full
179 as={SubmitButton}
180 className="mt4"
181 handleSubmit={handleSubmit}>
182 {isWarning =>
183 validating
184 ? 'Deriving...'
185 : submitting
186 ? 'Generating...'
187 : isWarning
188 ? 'Continue Activation'
189 : 'Go'
190 }
191 </Grid.Item>
193 {!activationAllowed && (
194 <Grid.Item full as={WarningBox} className="mt4">
195 For your security, please access Bridge on a desktop device.
196 </Grid.Item>
197 )}
198 </>
199 )}
200 </BridgeForm>
201 </Grid>
203 <FooterButton as={ForwardButton} onClick={goToLogin}>
204 Login
205 </FooterButton>
206 </View>
207 );