on 368f024 help sign in
bridge/src/views/Activate/ActivateCodeForm.tsx
233 lines 6.9 kB history raw
1import * as need from 'lib/need';
2import * as ob from 'urbit-ob';
3import { WARNING } from 'form/helpers';
4import { FORM_ERROR } from 'final-form';
5import { Just } from 'folktale/maybe';
6import BridgeForm from 'form/BridgeForm';
7import { TicketInput } from 'form/Inputs';
8import {
9 composeValidator,
10 buildPatqValidator,
11 hasErrors,
12 buildCheckboxValidator,
13} from 'form/validators';
14import FormError from 'form/FormError';
15import { CheckboxInput, Grid } from 'indigo-react';
16import { useCallback, useMemo, useRef } from 'react';
17import { useActivateFlow } from './ActivateFlow';
18import WarningBox from 'components/WarningBox';
19import useRoller from 'lib/useRoller';
20import useImpliedTicket from 'lib/useImpliedTicket';
21import { generateOwnershipWallet } from 'lib/walletgen';
22import { generateWallet } from 'lib/invite';
23import { walletFromMnemonic } from 'lib/wallet';
24import { DEFAULT_HD_PATH, POINT_PROXIES } from 'lib/constants';
25import useBreakpoints from 'lib/useBreakpoints';
26import { Ship } from '@urbit/roller-api';
27import { convertToInt } from 'lib/convertToInt';
28import { Box } from '@tlon/indigo-react';
29import { timeout } from 'lib/timeout';
30import withFadeable from './withFadeable';
31import ActivateButton from './ActivateButton';
33interface ActivateCodeFormProps {
34 afterSubmit: VoidFunction;
37const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
38 const { api, getPoints } = useRoller();
39 const { impliedTicket, impliedPatp } = useImpliedTicket();
40 const didWarn = useRef<boolean>(false);
42 const {
43 setDerivedPatp,
44 setDerivedPoint,
45 setDerivedPointDominion,
46 setDerivedWallet,
47 setInviteWallet,
48 setIsIn,
49 } = useActivateFlow();
51 // this is a pretty naive way to detect if we're on a mobile device
52 // (i.e. we're checking the width of the screen)
53 // but it will suffice for the 99% case and if someone wants to get around it
54 // well by golly they're allowed to turn their phone into landscape mode
55 // for this screen
56 const activationAllowed = useBreakpoints([false, true, true]);
58 const validateForm = useCallback(
59 (values, errors) => {
60 didWarn.current = false;
62 if (hasErrors(errors)) {
63 return errors;
64 }
65 },
66 [didWarn]
67 );
69 const validate = useMemo(
70 () =>
71 composeValidator(
72 { ticket: buildPatqValidator(), showTicket: buildCheckboxValidator() },
73 validateForm
74 ),
75 [validateForm]
76 );
78 const getTicketAndPoint = useCallback(
79 (invite: string) => {
80 const segments = invite.split('-');
81 return {
82 ticket: segments.slice(0, 4).join('-'),
83 point: impliedPatp
84 ? ob.patp2dec(impliedPatp)
85 : ob.patp2dec(`~${segments.slice(4, segments.length).join('-')}`),
86 };
87 },
88 [impliedPatp]
89 );
91 const loadPoints = useCallback(
92 async (address: string): Promise<Ship[]> => {
93 const owned = await getPoints(POINT_PROXIES.OWN, address);
94 const transferring = await getPoints(POINT_PROXIES.TRANSFER, address);
95 const incoming = [...owned, ...transferring];
96 console.log('incoming points', incoming);
97 return incoming;
98 },
99 [getPoints]
100 );
102 // derive and set our state on submission
103 const onSubmit = useCallback(
104 async values => {
105 // Allow UI animation to start
106 await timeout(42);
108 // Derive wallet
109 const { ticket, point } = getTicketAndPoint(
110 impliedTicket || values.ticket
111 );
112 const { seed } = await generateOwnershipWallet(point, ticket);
113 const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
114 setInviteWallet(inviteWallet);
115 const _inviteWallet = need.wallet(inviteWallet);
116 const inviteAddress = _inviteWallet.address;
118 let incoming = await loadPoints(inviteAddress);
120 // Set derived
121 if (incoming.length > 0) {
122 if (incoming.length > 1 && !didWarn.current) {
123 didWarn.current = true;
125 return {
126 [WARNING]:
127 'This invite code has multiple points available. ' +
128 "Once you've activated this point, " +
129 'activate the next with the same process.',
130 };
131 }
133 const point = convertToInt(incoming[0], 10);
134 const rollerPoint = await api.getPoint(point!);
136 setDerivedPatp(Just(ob.patp(point)));
137 setDerivedPoint(Just(point));
138 setDerivedPointDominion(Just(rollerPoint.dominion));
139 setDerivedWallet(Just(await generateWallet(point, true)));
140 setIsIn(false);
141 await timeout(100);
142 } else {
143 return {
144 [FORM_ERROR]:
145 'Invite code has no claimable point.\n' +
146 'Check your invite code and try again?',
147 };
148 }
149 },
150 [
151 api,
152 impliedTicket,
153 getTicketAndPoint,
154 loadPoints,
155 setDerivedPatp,
156 setDerivedPoint,
157 setDerivedPointDominion,
158 setDerivedWallet,
159 setInviteWallet,
160 setIsIn,
161 ]
162 );
164 const initialValues = useMemo(
165 () => ({ ticket: impliedTicket || '', showTicket: true }),
166 [impliedTicket]
167 );
169 return (
170 <Box
171 display="flex"
172 flexDirection="column"
173 flexWrap="nowrap"
174 height={'100%'}
175 justifyContent="flex-end">
176 <BridgeForm
177 validate={validate}
178 onSubmit={onSubmit}
179 afterSubmit={afterSubmit}
180 initialValues={initialValues}>
181 {({ valid, validating, values, submitting, handleSubmit }) => (
182 <>
183 {!impliedTicket && (
184 <>
185 <Grid.Item
186 full
187 as={TicketInput}
188 type={values.showTicket ? 'text' : 'password'}
189 name="ticket"
190 label="Activation Code"
191 disabled={!activationAllowed}
192 />
194 <Grid.Item
195 full
196 as={CheckboxInput}
197 name="showTicket"
198 label="Show"
199 />
200 </>
201 )}
203 <Grid.Item full as={FormError} />
205 <Box
206 display="flex"
207 flexDirection="column"
208 flexWrap="nowrap"
209 height={'100%'}
210 width={'100%'}
211 justifyContent="flex-end">
212 <ActivateButton
213 disabled={!valid || submitting}
214 onClick={handleSubmit}>
215 {submitting ? 'Generating...' : 'Claim'}
216 </ActivateButton>
217 </Box>
219 {!activationAllowed && (
220 <Grid.Item full as={WarningBox} className="mt4">
221 For your security, please access Bridge on a desktop device.
222 </Grid.Item>
223 )}
224 </>
225 )}
226 </BridgeForm>
227 </Box>
228 );
229};
231export default ActivateCodeForm;
233export const FadeableActivateCodeForm = withFadeable(ActivateCodeForm);