on c8e855d help sign in
bridge/src/views/Activate/ActivateCodeForm.tsx
239 lines 7.0 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 { generateWallet } from 'lib/walletgen';
22import { urbitWalletFromTicket, walletFromMnemonic } from 'lib/wallet';
23import { DEFAULT_HD_PATH, POINT_PROXIES } from 'lib/constants';
24import useBreakpoints from 'lib/useBreakpoints';
25import { Ship } from '@urbit/roller-api';
26import { convertToInt } from 'lib/convertToInt';
27import { Box } from '@tlon/indigo-react';
28import { timeout } from 'lib/timeout';
29import withFadeable from './withFadeable';
30import ActivateButton from './ActivateButton';
32interface ActivateCodeFormProps {
33 afterSubmit: VoidFunction;
36const ActivateCodeForm = ({ afterSubmit }: ActivateCodeFormProps) => {
37 const { api, getPoints } = useRoller();
38 const { impliedTicket, impliedPatp } = useImpliedTicket();
39 const didWarn = useRef<boolean>(false);
41 const {
42 setDerivedPatp,
43 setDerivedPoint,
44 setDerivedPointDominion,
45 setDerivedWallet,
46 setInviteWallet,
47 setIsIn,
48 setSendWallet,
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 );
113 const inviteWallet = Just(await urbitWalletFromTicket(ticket, point));
114 setInviteWallet(inviteWallet);
115 const _inviteWallet: UrbitWallet = need.wallet(inviteWallet);
116 const inviteAddress = _inviteWallet.ownership.keys.address;
118 const sendWallet = await walletFromMnemonic(
119 _inviteWallet.ownership.seed,
120 DEFAULT_HD_PATH
121 );
122 setSendWallet(sendWallet);
124 let incoming = await loadPoints(inviteAddress);
126 // Set derived
127 if (incoming.length > 0) {
128 if (incoming.length > 1 && !didWarn.current) {
129 didWarn.current = true;
131 return {
132 [WARNING]:
133 'This invite code has multiple points available. ' +
134 "Once you've activated this point, " +
135 'activate the next with the same process.',
136 };
137 }
139 const point = convertToInt(incoming[0], 10);
140 const rollerPoint = await api.getPoint(point!);
142 setDerivedPatp(Just(ob.patp(point)));
143 setDerivedPoint(Just(point));
144 setDerivedPointDominion(Just(rollerPoint.dominion));
145 setDerivedWallet(Just(await generateWallet(point, ticket, true)));
146 setIsIn(false);
147 await timeout(100);
148 } else {
149 return {
150 [FORM_ERROR]:
151 'Invite code has no claimable point.\n' +
152 'Check your invite code and try again?',
153 };
154 }
155 },
156 [
157 api,
158 impliedTicket,
159 getTicketAndPoint,
160 loadPoints,
161 setDerivedPatp,
162 setDerivedPoint,
163 setDerivedPointDominion,
164 setDerivedWallet,
165 setInviteWallet,
166 setIsIn,
167 ]
168 );
170 const initialValues = useMemo(
171 () => ({ ticket: impliedTicket || '', showTicket: true }),
172 [impliedTicket]
173 );
175 return (
176 <Box
177 display="flex"
178 flexDirection="column"
179 flexWrap="nowrap"
180 height={'100%'}
181 justifyContent="flex-end">
182 <BridgeForm
183 validate={validate}
184 onSubmit={onSubmit}
185 afterSubmit={afterSubmit}
186 initialValues={initialValues}>
187 {({ valid, validating, values, submitting, handleSubmit }) => (
188 <>
189 {!impliedTicket && (
190 <>
191 <Grid.Item
192 full
193 as={TicketInput}
194 type={values.showTicket ? 'text' : 'password'}
195 name="ticket"
196 label="Activation Code"
197 disabled={!activationAllowed}
198 />
200 <Grid.Item
201 full
202 as={CheckboxInput}
203 name="showTicket"
204 label="Show"
205 />
206 </>
207 )}
209 <Grid.Item full as={FormError} />
211 <Box
212 display="flex"
213 flexDirection="column"
214 flexWrap="nowrap"
215 height={'100%'}
216 width={'100%'}
217 justifyContent="flex-end">
218 <ActivateButton
219 disabled={!valid || submitting}
220 onClick={handleSubmit}>
221 {submitting ? 'Generating...' : 'Claim'}
222 </ActivateButton>
223 </Box>
225 {!activationAllowed && (
226 <Grid.Item full as={WarningBox} className="mt4">
227 For your security, please access Bridge on a desktop device.
228 </Grid.Item>
229 )}
230 </>
231 )}
232 </BridgeForm>
233 </Box>
234 );
235};
237export default ActivateCodeForm;
239export const FadeableActivateCodeForm = withFadeable(ActivateCodeForm);