on 4c8c607 help sign in
bridge/src/views/Login/Ticket.js
300 lines 8.3 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 {
7 Grid,
8 CheckboxInput,
9 Flex,
10 ToggleInput,
11 AccessoryIcon,
12} from 'indigo-react';
13import { FORM_ERROR } from 'final-form';
15import { useNetwork } from 'store/network';
16import { useWallet } from 'store/wallet';
17import { usePointCursor } from 'store/pointCursor';
19import * as need from 'lib/need';
20import { WALLET_TYPES, urbitWalletFromTicket } from 'lib/wallet';
21import useImpliedPoint from 'lib/useImpliedPoint';
22import useLoginView from 'lib/useLoginView';
23import patp2dec from 'lib/patp2dec';
24import timeout from 'lib/timeout';
26import BridgeForm from 'form/BridgeForm';
27import Condition from 'form/Condition';
28import { TicketInput, PassphraseInput, PointInput } from 'form/Inputs';
29import {
30 composeValidator,
31 buildCheckboxValidator,
32 buildPatqValidator,
33 buildShardValidator,
34 buildPassphraseValidator,
35 buildPointValidator,
36} from 'form/validators';
37import FormError from 'form/FormError';
38import SubmitButton from 'form/SubmitButton';
39import { WARNING } from 'form/helpers';
41import { ReactComponent as SecretShowIcon } from 'assets/secret-show.svg';
42import { ReactComponent as SecretHideIcon } from 'assets/secret-hidden.svg';
44function AdvancedOptions() {
45 return (
46 <Flex.Item as={Flex}>
47 <Condition when="useAdvanced" is={true}>
48 <Flex.Item
49 as={CheckboxInput}
50 inline
51 white
52 className="mr4"
53 name="usePassphrase"
54 label="Passphrase"
55 style={{ boxSizing: 'border-box' }}
56 />
57 <Flex.Item
58 as={CheckboxInput}
59 inline
60 white
61 className="mr4"
62 name="useShards"
63 label="Shards"
64 style={{ boxSizing: 'border-box' }}
65 />
66 </Condition>
67 <Flex.Item
68 as={ToggleInput}
69 name="useAdvanced"
70 label="Settings"
71 small
72 inverseLabel="Close"
73 />
74 </Flex.Item>
75 );
78function TicketInputAccessory({ name }) {
79 return (
80 <AccessoryIcon>
81 <ToggleInput
82 name={name}
83 className="mt1"
84 inverseLabel={<SecretShowIcon />}
85 label={<SecretHideIcon />}
86 />
87 </AccessoryIcon>
88 );
91export default function Ticket({ className, goHome }) {
92 useLoginView(WALLET_TYPES.TICKET);
94 const { contracts } = useNetwork();
95 const { setUrbitWallet } = useWallet();
96 const { setPointCursor } = usePointCursor();
97 const impliedPoint = useImpliedPoint();
98 const didWarn = useRef(false);
100 const validateForm = useCallback((values, errors) => {
101 didWarn.current = false;
103 if (errors.point) {
104 return errors;
105 }
107 if (values.useShards) {
108 if (errors.shard1 || errors.shard2 || errors.shard3) {
109 return errors;
110 }
111 const empty = ['shard1', 'shard2', 'shard3'].filter(s => !values[s]);
112 if (empty.length > 1) {
113 let errors = {};
114 empty.map(s => {
115 errors[s] = 'Please provide at least two out of three shards.';
116 });
117 return errors;
118 }
119 } else {
120 if (errors.ticket) {
121 return errors;
122 }
123 }
124 }, []);
126 const onSubmit = useCallback(
127 async values => {
128 const ticket = values.useShards
129 ? kg.combine(
130 [values.shard1, values.shard2, values.shard3].map(v =>
131 v === '' ? undefined : v
132 )
133 )
134 : values.ticket;
136 try {
137 const _contracts = need.contracts(contracts);
138 const point = patp2dec(values.point);
140 await timeout(16); // allow ui events to flush
141 const urbitWallet = await urbitWalletFromTicket(
142 ticket,
143 point,
144 values.passphrase
145 );
147 const [isOwner, isTransferProxy] = await Promise.all([
148 azimuth.azimuth.isOwner(
149 _contracts,
150 point,
151 urbitWallet.ownership.keys.address
152 ),
153 azimuth.azimuth.isTransferProxy(
154 _contracts,
155 point,
156 urbitWallet.ownership.keys.address
157 ),
158 ]);
160 const noPermissions = !isOwner && !isTransferProxy;
161 // warn the user
162 if (noPermissions && !didWarn.current) {
163 didWarn.current = true;
164 return {
165 [WARNING]:
166 'This wallet is not the owner or transfer proxy for this point.',
167 };
168 }
170 setUrbitWallet(Just(urbitWallet));
171 setPointCursor(Just(patp2dec(values.point)));
172 } catch (error) {
173 console.error(error);
174 return {
175 [FORM_ERROR]: `Unable to derive wallet from ${
176 values.useShards ? 'shards' : 'ticket'
177 }.`,
178 };
179 }
180 },
181 [contracts, setPointCursor, setUrbitWallet]
182 );
184 const validate = useMemo(
185 () =>
186 composeValidator(
187 {
188 useAdvanced: buildCheckboxValidator(),
189 usePassphrase: buildCheckboxValidator(),
190 useShards: buildCheckboxValidator(),
191 point: buildPointValidator(4),
192 ticket: buildPatqValidator(),
193 shard1: buildShardValidator(),
194 shard2: buildShardValidator(),
195 shard3: buildShardValidator(),
196 ticketHidden: buildCheckboxValidator(),
197 passphrase: buildPassphraseValidator(),
198 },
199 validateForm
200 ),
201 [validateForm]
202 );
204 const initialValues = useMemo(
205 () => ({
206 point: impliedPoint || '',
207 usePasshrase: false,
208 useShards: false,
209 ticketHidden: true,
210 useAdvanced: false,
211 }),
212 [impliedPoint]
213 );
215 return (
216 <Grid className={className}>
217 <BridgeForm
218 validate={validate}
219 onSubmit={onSubmit}
220 afterSubmit={goHome}
221 initialValues={initialValues}>
222 {({ handleSubmit, values, submitting }) => (
223 <>
224 <Grid.Item full as={PointInput} name="point" />
226 <Condition when="useShards" is={false}>
227 <Grid.Item
228 full
229 as={TicketInput}
230 hidden={values.ticketHidden}
231 labelAccessory={<AdvancedOptions />}
232 accessory={<TicketInputAccessory name="ticketHidden" />}
233 className="mt3"
234 name="ticket"
235 label="Master Ticket"
236 />
237 </Condition>
239 <Condition when="useShards" is={true}>
240 <Grid.Item
241 full
242 as={TicketInput}
243 accessory={<TicketInputAccessory name="ticketHidden" />}
244 hidden={values.ticketHidden}
245 labelAccessory={<AdvancedOptions />}
246 className="mt3"
247 name="shard1"
248 label="Shard 1"
249 />
250 <Grid.Item
251 full
252 as={TicketInput}
253 accessory={<TicketInputAccessory name="ticketHidden" />}
254 hidden={values.ticketHidden}
255 className="mt3"
256 name="shard2"
257 label="Shard 2"
258 />
259 <Grid.Item
260 full
261 as={TicketInput}
262 className="mt3"
263 accessory={<TicketInputAccessory name="ticketHidden" />}
264 hidden={values.ticketHidden}
265 name="shard3"
266 label="Shard 3"
267 />
268 </Condition>
270 <Condition when="usePassphrase" is={true}>
271 <Grid.Item
272 full
273 as={PassphraseInput}
274 className="mt3"
275 name="passphrase"
276 label="Wallet Passphrase"
277 />
278 </Condition>
280 <Grid.Item full as={FormError} />
282 <Grid.Item
283 full
284 as={SubmitButton}
285 center
286 handleSubmit={handleSubmit}>
287 {isWarning =>
288 submitting
289 ? 'Logging in...'
290 : isWarning
291 ? 'Login Anyway'
292 : 'Login'
293 }
294 </Grid.Item>
295 </>
296 )}
297 </BridgeForm>
298 </Grid>
299 );