on c19b2a8 help sign in
bridge/src/views/Login/Ticket.js
241 lines 7.0 kB history raw
1import React, { useCallback, useMemo, useRef } from 'react';
2import { Just } from 'folktale/maybe';
3import cn from 'classnames';
4import * as azimuth from 'azimuth-js';
5import * as kg from 'urbit-key-generation';
6import { Grid, CheckboxInput, Flex, ToggleInput } from 'indigo-react';
7import { FORM_ERROR } from 'final-form';
9import { useNetwork } from 'store/network';
10import { useWallet } from 'store/wallet';
11import { usePointCursor } from 'store/pointCursor';
13import * as need from 'lib/need';
14import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
15import useImpliedPoint from 'lib/useImpliedPoint';
16import useLoginView from 'lib/useLoginView';
17import patp2dec from 'lib/patp2dec';
18import timeout from 'lib/timeout';
20import BridgeForm from 'form/BridgeForm';
21import Condition from 'form/Condition';
22import { TicketInput, PassphraseInput, PointInput } from 'form/Inputs';
23import {
24 composeValidator,
25 buildCheckboxValidator,
26 buildPatqValidator,
27 buildPassphraseValidator,
28 buildPointValidator,
29} from 'form/validators';
30import FormError from 'form/FormError';
31import SubmitButton from 'form/SubmitButton';
32import { WARNING } from 'form/helpers';
34export default function Ticket({ className, goHome }) {
35 useLoginView(WALLET_TYPES.TICKET);
37 const { contracts } = useNetwork();
38 const { setUrbitWallet } = useWallet();
39 const { setPointCursor } = usePointCursor();
40 const impliedPoint = useImpliedPoint();
41 const didWarn = useRef(false);
43 const validateForm = useCallback((values, errors) => {
44 didWarn.current = false;
46 if (errors.point) {
47 return errors;
48 }
50 if (values.useShards) {
51 if (errors.shard1 || errors.shard2 || errors.shard3) {
52 return errors;
53 }
54 } else {
55 if (errors.ticket) {
56 return errors;
57 }
58 }
59 }, []);
61 const onSubmit = useCallback(
62 async values => {
63 const ticket = values.useShards
64 ? kg.combine([values.shard1, values.shard2, values.shard3])
65 : values.ticket;
67 try {
68 const _contracts = need.contracts(contracts);
69 const point = patp2dec(values.point);
71 await timeout(16); // allow ui events to flush
72 const urbitWallet = await urbitWalletFromTicket(
73 ticket,
74 point,
75 values.passphrase
76 );
78 const [isOwner, isTransferProxy] = await Promise.all([
79 azimuth.azimuth.isOwner(
80 _contracts,
81 point,
82 urbitWallet.ownership.keys.address
83 ),
84 azimuth.azimuth.isTransferProxy(
85 _contracts,
86 point,
87 urbitWallet.ownership.keys.address
88 ),
89 ]);
91 const noPermissions = !isOwner && !isTransferProxy;
92 // warn the user
93 if (noPermissions && !didWarn.current) {
94 didWarn.current = true;
95 return {
96 [WARNING]:
97 'This wallet is not the owner or transfer proxy for this point.',
98 };
99 }
101 setUrbitWallet(Just(urbitWallet));
102 setPointCursor(Just(patp2dec(values.point)));
103 } catch (error) {
104 console.error(error);
105 return {
106 [FORM_ERROR]: `Unable to derive wallet from ${
107 values.useShards ? 'shards' : 'ticket'
108 }.`,
109 };
110 }
111 },
112 [contracts, setPointCursor, setUrbitWallet]
113 );
115 const validate = useMemo(
116 () =>
117 composeValidator(
118 {
119 useAdvanced: buildCheckboxValidator(),
120 usePassphrase: buildCheckboxValidator(),
121 useShards: buildCheckboxValidator(),
122 point: buildPointValidator(4),
123 ticket: buildPatqValidator(),
124 shard1: buildPatqValidator(),
125 shard2: buildPatqValidator(),
126 shard3: buildPatqValidator(),
127 showTicket: buildCheckboxValidator(),
128 passphrase: buildPassphraseValidator(),
129 },
130 validateForm
131 ),
132 [validateForm]
133 );
135 const initialValues = useMemo(
136 () => ({
137 point: impliedPoint || '',
138 usePasshrase: false,
139 useShards: false,
140 showTicket: true,
141 useAdvanced: false,
142 }),
143 [impliedPoint]
144 );
146 return (
147 <Grid className={cn('mt4', className)}>
148 <BridgeForm
149 validate={validate}
150 onSubmit={onSubmit}
151 afterSubmit={goHome}
152 initialValues={initialValues}>
153 {({ handleSubmit, values, submitting }) => (
154 <>
155 <Grid.Item full as={PointInput} name="point" />
157 <Condition when="useShards" is={false}>
158 <Grid.Item
159 full
160 as={TicketInput}
161 type={values.showTicket ? 'text' : 'password'}
162 name="ticket"
163 label="Master Ticket"
164 />
165 </Condition>
167 <Condition when="useShards" is={true}>
168 <Grid.Item
169 full
170 as={TicketInput}
171 type={values.showTicket ? 'text' : 'password'}
172 name="shard1"
173 label="Shard 1"
174 />
175 <Grid.Item
176 full
177 as={TicketInput}
178 type={values.showTicket ? 'text' : 'password'}
179 name="shard2"
180 label="Shard 2"
181 />
182 <Grid.Item
183 full
184 as={TicketInput}
185 type={values.showTicket ? 'text' : 'password'}
186 name="shard3"
187 label="Shard 3"
188 />
189 </Condition>
191 <Grid.Item full as={CheckboxInput} name="showTicket" label="Show" />
193 <Condition when="usePassphrase" is={true}>
194 <Grid.Item
195 full
196 as={PassphraseInput}
197 name="passphrase"
198 label="Wallet Passphrase"
199 />
200 </Condition>
202 <Grid.Item full as={FormError} />
204 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
205 {isWarning =>
206 submitting
207 ? 'Logging in...'
208 : isWarning
209 ? 'Login Anyway'
210 : 'Continue'
211 }
212 </Grid.Item>
214 <Grid.Item full as={Flex} justify="between">
215 <Flex.Item
216 as={ToggleInput}
217 name="useAdvanced"
218 label="Advanced"
219 inverseLabel="Hide"
220 />
221 <Condition when="useAdvanced" is={true}>
222 <Flex.Item as={Flex}>
223 <Flex.Item
224 as={CheckboxInput}
225 name="usePassphrase"
226 label="Passphrase"
227 />
228 <Flex.Item
229 as={CheckboxInput}
230 name="useShards"
231 label="Shards"
232 />
233 </Flex.Item>
234 </Condition>
235 </Grid.Item>
236 </>
237 )}
238 </BridgeForm>
239 </Grid>
240 );