@@ -0,0 +1,303 @@
1+import { useCallback, useEffect, useMemo, useRef } from 'react';
2+import { Just } from 'folktale/maybe';
3+import * as azimuth from 'azimuth-js';
4+import { Grid, H4, P, CheckboxInput } from 'indigo-react';
5+import { FORM_ERROR } from 'final-form';
7+import View from 'components/View';
8+import { ForwardButton } from 'components/Buttons';
9+import WarningBox from 'components/WarningBox';
10+import FooterButton from 'components/FooterButton';
11+import ActivateView from './ActivateView';
13+import { useNetwork } from 'store/network';
14+import { useHistory } from 'store/history';
16+import * as need from 'lib/need';
17+import { ROUTE_NAMES } from 'lib/routeNames';
18+import { walletFromMnemonic } from 'lib/wallet';
19+import { DEFAULT_HD_PATH, POINT_DOMINIONS, POINT_PROXIES } from 'lib/constants';
20+import { generateWallet } from 'lib/invite';
21+import { generateTemporaryOwnershipWallet } from 'lib/walletgen';
22+import { useLocalRouter } from 'lib/LocalRouter';
23+import useImpliedTicket from 'lib/useImpliedTicket';
24+import { timeout } from 'lib/timeout';
25+import useHasDisclaimed from 'lib/useHasDisclaimed';
26+import useBreakpoints from 'lib/useBreakpoints';
28+import BridgeForm from 'form/BridgeForm';
29+import SubmitButton from 'form/SubmitButton';
30+import { TicketInput } from 'form/Inputs';
35+ buildCheckboxValidator,
36+} from 'form/validators';
37+import FormError from 'form/FormError';
39+import { useActivateFlow } from './ActivateFlow';
40+import { WARNING } from 'form/helpers';
41+import { convertToInt } from 'lib/convertToInt';
42+import PointPresenter from './PointPresenter';
43+import useRoller from 'lib/useRoller';
44+import { Ship } from '@urbit/roller-api';
45+import { Box, Text } from '@tlon/indigo-react';
46+import ActivateHeader from './ActivateHeader';
48+export default function ActivateCode() {
49+ const history = useHistory();
50+ const { names, push } = useLocalRouter();
51+ const { contracts } = useNetwork();
56+ } = useImpliedTicket();
57+ const [hasDisclaimed] = useHasDisclaimed();
58+ const didWarn = useRef(false);
59+ const { api, getPoints } = useRoller();
65+ } = useActivateFlow();
66+ // this is a pretty naive way to detect if we're on a mobile device
67+ // (i.e. we're checking the width of the screen)
68+ // but it will suffice for the 99% case and if someone wants to get around it
69+ // well by golly they're allowed to turn their phone into landscape mode
71+ const activationAllowed = useBreakpoints([false, true, true]);
73+ const goToLogin = useCallback(() => history.popAndPush(ROUTE_NAMES.LOGIN), [
77+ const goToPassport = useCallback(() => {
78+ if (!hasDisclaimed) {
79+ push(names.DISCLAIMER, { next: names.PASSPORT });
81+ push(names.PASSPORT);
83+ }, [hasDisclaimed, names.DISCLAIMER, names.PASSPORT, push]);
85+ const validateForm = useCallback((values, errors) => {
86+ didWarn.current = false;
88+ if (hasErrors(errors)) {
93+ const validate = useMemo(
96+ { ticket: buildPatqValidator(), showTicket: buildCheckboxValidator() },
102+ const loadL1Points = useCallback(
103+ async (address: string): Promise<number[]> => {
104+ const _contracts = need.contracts(contracts);
105+ const owned = await azimuth.azimuth.getOwnedPoints(_contracts, address);
106+ const transferring = await azimuth.azimuth.getTransferringFor(
110+ const incoming = [...owned, ...transferring];
111+ console.log('incoming L1 points', incoming);
117+ const loadl2points = useCallback(
118+ async (address: string): Promise<Ship[]> => {
119+ const owned = await getPoints(POINT_PROXIES.OWN, address);
120+ const transferring = await getPoints(POINT_PROXIES.TRANSFER, address);
121+ const incoming = [...owned, ...transferring];
122+ console.log('incoming L2 points', incoming);
128+ // derive and set our state on submission
129+ const onSubmit = useCallback(
131+ await timeout(16); // allow the ui changes to flush before we lag it out
134+ // TODO: fallback logic to first use implied values if present, otherwise form values?
135+ const ticket = impliedTicket || values.ticket;
136+ const { seed } = await generateTemporaryOwnershipWallet(ticket);
137+ const inviteWallet = walletFromMnemonic(seed, DEFAULT_HD_PATH);
138+ setInviteWallet(inviteWallet);
139+ const _inviteWallet = need.wallet(inviteWallet);
140+ const inviteAddress = _inviteWallet.address;
142+ // Query for points depending on point's dominion
143+ const rollerPoint = await api.getPoint(impliedAzimuthPoint!);
144+ let incoming: number[] | Ship[];
145+ if (rollerPoint.dominion === POINT_DOMINIONS.L2) {
146+ incoming = await loadl2points(inviteAddress);
148+ incoming = await loadL1Points(inviteAddress);
152+ if (incoming.length > 0) {
153+ if (incoming.length > 1 && !didWarn.current) {
154+ didWarn.current = true;
158+ 'This invite code has multiple points available. ' +
159+ "Once you've activated this point, " +
160+ 'activate the next with the same process.',
164+ const point = convertToInt(incoming[0], 10);
165+ console.log('submitting point', point);
167+ setDerivedPoint(Just(point));
168+ setDerivedWallet(Just(await generateWallet(point, true)));
172+ 'Invite code has no claimable point.\n' +
173+ 'Check your invite code and try again?',
179+ impliedAzimuthPoint,
189+ const initialValues = useMemo(
190+ () => ({ ticket: impliedTicket || '', showTicket: true }),
195+ const fetchRollerPoint = async () => {
196+ if (!impliedAzimuthPoint) {
199+ const p = await api.getPoint(impliedAzimuthPoint!);
204+ }, [api, impliedAzimuthPoint]);
206+ const footer = useMemo(() => {
210+ flexDirection="column"
212+ justifyContent="flex-end">
217+ afterSubmit={goToPassport}
218+ initialValues={initialValues}>
219+ {({ validating, values, submitting, handleSubmit }) => (
221+ {!impliedTicket && (
226+ type={values.showTicket ? 'text' : 'password'}
228+ label="Activation Code"
229+ disabled={!activationAllowed}
241+ <Grid.Item full as={FormError} />
247+ handleSubmit={handleSubmit}>
254+ ? 'Continue Activation'
259+ {!activationAllowed && (
260+ <Grid.Item full as={WarningBox} className="mt4">
261+ For your security, please access Bridge on a desktop device.
282+ header={<ActivateHeader copy={'Welcome. This is your Urbit.'} />}
285+ alignItems={'center'}
287+ flexDirection={'column'}
290+ justifyContent={'center'}>
291+ {!impliedTicket && (
292+ <Text className="mb2">Enter your activation code to continue.</Text>
294+ {impliedPatp && <PointPresenter patp={impliedPatp} />}
298+ <FooterButton as={ForwardButton} onClick={goToLogin}>