on 333d26f help sign in
bridge/src/views/Activate/ActivateCode.js
239 lines 7.2 kB history raw
1import { useCallback, useMemo, useRef } from 'react';
2import { Just } 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 WarningBox from 'components/WarningBox';
10import FooterButton from 'components/FooterButton';
12import { useNetwork } from 'store/network';
13import { useHistory } from 'store/history';
15import * as need from 'lib/need';
16import { ROUTE_NAMES } from 'lib/routeNames';
17import { walletFromMnemonic } from 'lib/wallet';
18import { DEFAULT_HD_PATH } from 'lib/constants';
19import { generateWallet } from 'lib/invite';
20import { generateOwnershipWallet } 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';
41import { patp2dec } from 'lib/patp2dec';
43export default function ActivateCode() {
44 const history = useHistory();
45 const { names, push } = useLocalRouter();
46 const { contracts } = useNetwork();
47 const { impliedPoint, impliedTicket } = useImpliedTicket();
48 const [hasDisclaimed] = useHasDisclaimed();
49 const didWarn = useRef(false);
51 const {
52 setDerivedPoint,
53 setDerivedWallet,
54 setInviteWallet,
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 // derive and 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 // TODO: fallback logic to first use implied values if present, otherwise form values?
98 // Derive wallet
99 const impliedAzimuthPoint = patp2dec(impliedPoint);
100 const _contracts = need.contracts(contracts);
101 const { seed } = await generateOwnershipWallet(
102 impliedAzimuthPoint,
103 impliedTicket
104 );
105 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
106 const _inviteWallet = need.wallet(inviteWallet);
108 // Load associated points
109 const owned = await azimuth.azimuth.getOwnedPoints(
110 _contracts,
111 _inviteWallet.address
112 );
113 const transferring = await azimuth.azimuth.getTransferringFor(
114 _contracts,
115 _inviteWallet.address
116 );
118 const incoming = [...owned, ...transferring];
120 setInviteWallet(inviteWallet);
122 // Set derived
123 if (incoming.length > 0) {
124 if (incoming.length > 1 && !didWarn.current) {
125 didWarn.current = true;
127 return {
128 [WARNING]:
129 'This invite code has multiple points available. ' +
130 "Once you've activated this point, " +
131 'activate the next with the same process.',
132 };
133 }
135 const point = convertToInt(incoming[0], 10);
137 setDerivedPoint(Just(point));
138 setDerivedWallet(Just(await generateWallet(point, true)));
139 } else {
140 return {
141 [FORM_ERROR]:
142 'Invite code has no claimable point.\n' +
143 'Check your invite code and try again?',
144 };
145 }
146 },
147 [
148 contracts,
149 impliedPoint,
150 impliedTicket,
151 setDerivedPoint,
152 setDerivedWallet,
153 setInviteWallet,
154 ]
155 );
157 const initialValues = useMemo(
158 () => ({ ticket: impliedTicket || '', showTicket: true }),
159 [impliedTicket]
160 );
162 return (
163 <View inset>
164 <Grid>
165 <Grid.Item full as={H4} className="mt3">
166 Welcome. This is your Urbit.
167 </Grid.Item>
168 {!impliedTicket && (
169 <Grid.Item full as={P} className="mb2">
170 Enter your activation code to continue.
171 </Grid.Item>
172 )}
174 {impliedPoint && (
175 <Grid.Item full as={P} className="mb2">
176 {impliedPoint}
177 </Grid.Item>
178 )}
179 <BridgeForm
180 validate={validate}
181 onSubmit={onSubmit}
182 afterSubmit={goToPassport}
183 initialValues={initialValues}>
184 {({ validating, values, submitting, handleSubmit }) => (
185 <>
186 {!impliedTicket && (
187 <>
188 <Grid.Item
189 full
190 as={TicketInput}
191 type={values.showTicket ? 'text' : 'password'}
192 name="ticket"
193 label="Activation Code"
194 disabled={!activationAllowed}
195 />
197 <Grid.Item
198 full
199 as={CheckboxInput}
200 name="showTicket"
201 label="Show"
202 />
203 </>
204 )}
206 <Grid.Item full as={FormError} />
208 <Grid.Item
209 full
210 as={SubmitButton}
211 className="mt4"
212 handleSubmit={handleSubmit}>
213 {isWarning =>
214 validating
215 ? 'Deriving...'
216 : submitting
217 ? 'Generating...'
218 : isWarning
219 ? 'Continue Activation'
220 : 'Claim'
221 }
222 </Grid.Item>
224 {!activationAllowed && (
225 <Grid.Item full as={WarningBox} className="mt4">
226 For your security, please access Bridge on a desktop device.
227 </Grid.Item>
228 )}
229 </>
230 )}
231 </BridgeForm>
232 </Grid>
234 <FooterButton as={ForwardButton} onClick={goToLogin}>
235 Login
236 </FooterButton>
237 </View>
238 );