on eb070bb help sign in
bridge/src/views/Login/Ticket.js
287 lines 7.9 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 buildPassphraseValidator,
34 buildPointValidator,
35} from 'form/validators';
36import FormError from 'form/FormError';
37import SubmitButton from 'form/SubmitButton';
38import { WARNING } from 'form/helpers';
40import { ReactComponent as SecretShowIcon } from 'assets/secret-show.svg';
41import { ReactComponent as SecretHideIcon } from 'assets/secret-hidden.svg';
43function AdvancedOptions() {
44 return (
45 <Flex.Item as={Flex}>
46 <Condition when="useAdvanced" is={true}>
47 <Flex.Item
48 as={CheckboxInput}
49 inline
50 white
51 className="mr4"
52 name="usePassphrase"
53 label="Passphrase"
54 style={{ boxSizing: 'border-box' }}
55 />
56 <Flex.Item
57 as={CheckboxInput}
58 inline
59 white
60 className="mr4"
61 name="useShards"
62 label="Shards"
63 style={{ boxSizing: 'border-box' }}
64 />
65 </Condition>
66 <Flex.Item
67 as={ToggleInput}
68 name="useAdvanced"
69 label="Settings"
70 small
71 inverseLabel="Close"
72 />
73 </Flex.Item>
74 );
77function TicketInputAccessory({ name }) {
78 return (
79 <AccessoryIcon>
80 <ToggleInput
81 name={name}
82 className="mt1"
83 inverseLabel={<SecretShowIcon />}
84 label={<SecretHideIcon />}
85 />
86 </AccessoryIcon>
87 );
90export default function Ticket({ className, goHome }) {
91 useLoginView(WALLET_TYPES.TICKET);
93 const { contracts } = useNetwork();
94 const { setUrbitWallet } = useWallet();
95 const { setPointCursor } = usePointCursor();
96 const impliedPoint = useImpliedPoint();
97 const didWarn = useRef(false);
99 const validateForm = useCallback((values, errors) => {
100 didWarn.current = false;
102 if (errors.point) {
103 return errors;
104 }
106 if (values.useShards) {
107 if (errors.shard1 || errors.shard2 || errors.shard3) {
108 return errors;
109 }
110 } else {
111 if (errors.ticket) {
112 return errors;
113 }
114 }
115 }, []);
117 const onSubmit = useCallback(
118 async values => {
119 const ticket = values.useShards
120 ? kg.combine([values.shard1, values.shard2, values.shard3])
121 : values.ticket;
123 try {
124 const _contracts = need.contracts(contracts);
125 const point = patp2dec(values.point);
127 await timeout(16); // allow ui events to flush
128 const urbitWallet = await urbitWalletFromTicket(
129 ticket,
130 point,
131 values.passphrase
132 );
134 const [isOwner, isTransferProxy] = await Promise.all([
135 azimuth.azimuth.isOwner(
136 _contracts,
137 point,
138 urbitWallet.ownership.keys.address
139 ),
140 azimuth.azimuth.isTransferProxy(
141 _contracts,
142 point,
143 urbitWallet.ownership.keys.address
144 ),
145 ]);
147 const noPermissions = !isOwner && !isTransferProxy;
148 // warn the user
149 if (noPermissions && !didWarn.current) {
150 didWarn.current = true;
151 return {
152 [WARNING]:
153 'This wallet is not the owner or transfer proxy for this point.',
154 };
155 }
157 setUrbitWallet(Just(urbitWallet));
158 setPointCursor(Just(patp2dec(values.point)));
159 } catch (error) {
160 console.error(error);
161 return {
162 [FORM_ERROR]: `Unable to derive wallet from ${
163 values.useShards ? 'shards' : 'ticket'
164 }.`,
165 };
166 }
167 },
168 [contracts, setPointCursor, setUrbitWallet]
169 );
171 const validate = useMemo(
172 () =>
173 composeValidator(
174 {
175 useAdvanced: buildCheckboxValidator(),
176 usePassphrase: buildCheckboxValidator(),
177 useShards: buildCheckboxValidator(),
178 point: buildPointValidator(4),
179 ticket: buildPatqValidator(),
180 shard1: buildPatqValidator(),
181 shard2: buildPatqValidator(),
182 shard3: buildPatqValidator(),
183 ticketHidden: buildCheckboxValidator(),
184 passphrase: buildPassphraseValidator(),
185 },
186 validateForm
187 ),
188 [validateForm]
189 );
191 const initialValues = useMemo(
192 () => ({
193 point: impliedPoint || '',
194 usePasshrase: false,
195 useShards: false,
196 ticketHidden: true,
197 useAdvanced: false,
198 }),
199 [impliedPoint]
200 );
202 return (
203 <Grid className={className}>
204 <BridgeForm
205 validate={validate}
206 onSubmit={onSubmit}
207 afterSubmit={goHome}
208 initialValues={initialValues}>
209 {({ handleSubmit, values, submitting }) => (
210 <>
211 <Grid.Item full as={PointInput} name="point" />
213 <Condition when="useShards" is={false}>
214 <Grid.Item
215 full
216 as={TicketInput}
217 hidden={values.ticketHidden}
218 labelAccessory={<AdvancedOptions />}
219 accessory={<TicketInputAccessory name="ticketHidden" />}
220 className="mt3"
221 name="ticket"
222 label="Master Ticket"
223 />
224 </Condition>
226 <Condition when="useShards" is={true}>
227 <Grid.Item
228 full
229 as={TicketInput}
230 accessory={<TicketInputAccessory name="ticketHidden" />}
231 hidden={values.ticketHidden}
232 labelAccessory={<AdvancedOptions />}
233 className="mt3"
234 name="shard1"
235 label="Shard 1"
236 />
237 <Grid.Item
238 full
239 as={TicketInput}
240 accessory={<TicketInputAccessory name="ticketHidden" />}
241 hidden={values.ticketHidden}
242 className="mt3"
243 name="shard2"
244 label="Shard 2"
245 />
246 <Grid.Item
247 full
248 as={TicketInput}
249 className="mt3"
250 accessory={<TicketInputAccessory name="ticketHidden" />}
251 hidden={values.ticketHidden}
252 name="shard3"
253 label="Shard 3"
254 />
255 </Condition>
257 <Condition when="usePassphrase" is={true}>
258 <Grid.Item
259 full
260 as={PassphraseInput}
261 className="mt3"
262 name="passphrase"
263 label="Wallet Passphrase"
264 />
265 </Condition>
267 <Grid.Item full as={FormError} />
269 <Grid.Item
270 full
271 as={SubmitButton}
272 center
273 handleSubmit={handleSubmit}>
274 {isWarning =>
275 submitting
276 ? 'Logging in...'
277 : isWarning
278 ? 'Login Anyway'
279 : 'Login'
280 }
281 </Grid.Item>
282 </>
283 )}
284 </BridgeForm>
285 </Grid>
286 );