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