on aee35bb help sign in
bridge/src/views/InviteTicket.js
435 lines 12 kB history raw
1import { Just, Nothing } from 'folktale/maybe';
2import React from 'react';
3import * as azimuth from 'azimuth-js';
4import * as need from 'lib/need';
6import View from 'components/View';
8import {
9 InnerLabel,
10 Warning,
11 TicketInput,
12 VerifyTicketInput,
13 Button,
14 Passport,
15} from '../components/old/Base';
17import { randomPatq, compose } from '../lib/lib';
18import { ROUTE_NAMES } from '../lib/routeNames';
19import { withHistory } from '../store/history';
20import {
21 DEFAULT_HD_PATH,
22 ownershipWalletFromTicket,
23 walletFromMnemonic,
24} from '../lib/wallet';
25import { BRIDGE_ERROR } from '../lib/error';
26import {
27 INVITE_STAGES,
28 WALLET_STATES,
29 TRANSACTION_STATES,
30} from '../lib/invite';
31import { generateWallet, startTransactions } from '../lib/invite';
32import { withNetwork } from '../store/network';
33import { withWallet } from '../store/wallet';
34import Grid from 'components/Grid';
36class InviteTicket extends React.Component {
37 constructor(props) {
38 super(props);
40 this.state = {
41 inviteTicket: '',
42 inviteWallet: Nothing(),
43 verifyTicket: '',
44 realTicket: '',
45 realPoint: Nothing(),
46 realWallet: Nothing(),
47 walletReady: false,
48 walletStates: [],
49 errors: [],
50 stage: INVITE_STAGES.INVITE_LOGIN,
51 transactionProgress: TRANSACTION_STATES.GENERATING,
52 };
54 this.pointPlaceholder = randomPatq(4);
55 this.ticketPlaceholder = randomPatq(12);
57 this.handleInviteTicketInput = this.handleInviteTicketInput.bind(this);
58 this.handleVerifyTicketInput = this.handleVerifyTicketInput.bind(this);
59 this.updateProgress = this.updateProgress.bind(this);
60 this.pushWalletState = this.pushWalletState.bind(this);
61 this.navigateLogin = this.navigateLogin.bind(this);
62 }
64 pushWalletState(walletState) {
65 this.setState({
66 walletStates: this.state.walletStates.concat(walletState),
67 });
68 }
70 navigateLogin() {
71 this.props.history.pop();
72 this.props.history.push(ROUTE_NAMES.NETWORK);
73 }
75 handleInviteTicketInput(inviteTicket) {
76 this.setState({ inviteTicket });
77 }
79 handleVerifyTicketInput(verifyTicket) {
80 this.setState({ verifyTicket });
81 }
83 updateTransactionProgress(transactionProgress) {
84 this.setState({ transactionProgress });
85 }
87 async unlockInviteWallet(inviteTicket) {
88 this.setState({
89 walletStates: this.state.walletStates.concat(WALLET_STATES.UNLOCKING),
90 stage: INVITE_STAGES.INVITE_WALLET,
91 });
93 //NOTE ~zod because tmp wallet
94 const ownership = await ownershipWalletFromTicket(inviteTicket, 0);
95 const mnemonic = ownership.seed;
96 //TODO isn't all this accessible in the ownership object?
97 const inviteWallet = walletFromMnemonic(
98 mnemonic,
99 DEFAULT_HD_PATH
100 ).matchWith({
101 Just: w => w.value,
102 Nothing: () => {
103 throw BRIDGE_ERROR.MISSING_WALLET;
104 },
105 });
107 const addr = inviteWallet.address;
109 const contracts = need.contracts(this.props.contracts);
111 const incoming = await azimuth.azimuth.getTransferringFor(contracts, addr);
113 this.setState({
114 walletStates: this.state.walletStates.concat(WALLET_STATES.GENERATING),
115 });
117 let realPoint = Nothing();
118 let realWallet = Nothing();
119 let errors = [];
121 if (incoming.length > 0) {
122 let pointNum = parseInt(incoming[0], 10);
123 realPoint = Just(pointNum);
125 this.setState({
126 realPoint,
127 });
129 let genWallet = await generateWallet(pointNum);
131 realWallet = Just(genWallet);
132 if (incoming.length > 1) {
133 //TODO notify user "...and others / ticket reusable"
134 }
135 } else {
136 errors = [
137 'Invite code has no claimable ship. Check your invite code and try again.',
138 ];
139 }
141 this.setState({
142 realWallet,
143 inviteWallet: Just(inviteWallet),
144 // dev mode: uncomment this out to autofill verified ticket
145 verifyTicket: !Nothing.hasInstance(realWallet)
146 ? realWallet.value.ticket
147 : '',
148 walletStates: this.state.walletStates.concat(WALLET_STATES.RENDERING),
149 errors,
150 });
151 }
153 getTicketElement() {
154 const phTick = this.ticketPlaceholder;
156 let isLogin = this.state.stage === INVITE_STAGES.INVITE_LOGIN;
157 let isVerify = this.state.stage === INVITE_STAGES.INVITE_VERIFY;
159 if (isLogin || isVerify) {
160 let value = isLogin ? this.state.inviteTicket : this.state.verifyTicket;
161 let handler = isLogin
162 ? this.handleInviteTicketInput
163 : this.handleVerifyTicketInput;
164 let label = isLogin ? 'Activation code' : 'ticket';
166 // if realWallet is Just, then VerifyTicketInput appropriate contents; if Nothing, remain Nothing
167 const Verified = this.state.realWallet.map(realWallet =>
168 VerifyTicketInput(realWallet.ticket)
169 );
171 // Verified.getOrElse(TicketInput) falls back to TicketInput if Verified is Nothing
172 const TicketElem = isVerify
173 ? Verified.getOrElse(TicketInput)
174 : TicketInput;
176 return (
177 <TicketElem
178 className="mono mt-8"
179 prop-size="md"
180 prop-format="innerLabel"
181 type="text"
182 autoFocus
183 name="invite"
184 placeholder={`e.g. ${phTick}`}
185 value={value}
186 onChange={handler}>
187 <InnerLabel>{label}</InnerLabel>
188 </TicketElem>
189 );
190 } else {
191 return null;
192 }
193 }
195 getStageText() {
196 const { stage } = this.state;
198 let progressWidth = this.state.transactionProgress.pct;
200 const stageText =
201 stage === INVITE_STAGES.INVITE_LOGIN ? (
202 <h1 className="text-700 mt-15">Welcome</h1>
203 ) : stage === INVITE_STAGES.INVITE_WALLET ? (
204 <div>
205 <h1 className="fs-6 lh-8 mb-3">Passport</h1>
206 <p>
207 A passport is your digital identity. You will use your passport to
208 access your true computer, send payments, and administer your
209 identity. So naturally, you must keep this secure.
210 </p>
211 <p>
212 After you’ve downloaded your passport, back up the ticket manually
213 or store on a trusted device.
214 </p>
215 </div>
216 ) : stage === INVITE_STAGES.INVITE_VERIFY ? (
217 <div>
218 <h1 className="fs-6 lh-8 mb-3">Verify Passport</h1>
219 <p>
220 Afer you download your passport, verify your custody. Your passport
221 should be a folder of image files. One of them is your Master
222 Ticket. Open it and enter the 4 word phrase below (with hyphens).
223 </p>
224 </div>
225 ) : INVITE_STAGES.INVITE_TRANSACTIONS ? (
226 this.state.transactionProgress.label ===
227 TRANSACTION_STATES.DONE.label ? (
228 <div>
229 <h1 className="fs-6 lh-8 mb-3">
230 <span>Success</span>
231 <span className="ml-4 green">✓</span>
232 </h1>
233 <p>Transferring to Bridge...</p>
234 </div>
235 ) : (
236 <div>
237 <h1 className="fs-6 lh-8 mb-3">Submitting</h1>
238 <p className="mt-4 mb-4">
239 This step can take up to five minutes. Please do not leave this
240 page until the transactions are complete.
241 </p>
242 <div className="passport-progress mb-2">
243 <div
244 className="passport-progress-filled"
245 style={{ width: progressWidth }}
246 />
247 </div>
248 <div className="flex justify-between">
249 <div className="text-mono text-sm lh-6 green-dark">
250 {this.state.transactionProgress.label}
251 </div>
252 </div>
253 </div>
254 )
255 ) : null;
257 return stageText;
258 }
260 updateProgress(notification) {
261 let newState =
262 notification.type === 'progress'
263 ? { transactionProgress: notification.value }
264 : notification.type === 'notify'
265 ? { errors: [notification.value] }
266 : {};
268 this.setState(newState);
269 }
271 getContinueButton() {
272 const {
273 realPoint,
274 realWallet,
275 inviteWallet,
276 stage,
277 walletStates,
278 verifyTicket,
279 inviteTicket,
280 } = this.state;
281 const { web3, contracts, setUrbitWallet } = this.props;
283 const btnColor = this.state.walletStates.includes(WALLET_STATES.PAPER_READY)
284 ? 'green'
285 : 'black';
287 const clickHandler =
288 stage === INVITE_STAGES.INVITE_LOGIN
289 ? () => {
290 this.unlockInviteWallet(inviteTicket);
291 }
292 : stage === INVITE_STAGES.INVITE_WALLET
293 ? () => {
294 this.setState({
295 stage: INVITE_STAGES.INVITE_VERIFY,
296 });
297 }
298 : stage === INVITE_STAGES.INVITE_VERIFY
299 ? () => {
300 this.setState({
301 stage: INVITE_STAGES.INVITE_TRANSACTIONS,
302 walletStates: this.state.walletStates.concat(
303 WALLET_STATES.TRANSACTIONS
304 ),
305 });
307 startTransactions({
308 inviteWalletM: inviteWallet,
309 realWalletM: realWallet,
310 realPointM: realPoint,
311 realTicket: verifyTicket,
312 web3,
313 contracts,
314 setUrbitWallet,
315 updateProgress: this.updateProgress,
316 }).then(() => {
317 setTimeout(() => {
318 this.props.history.pop();
319 }, 5000);
320 });
321 }
322 : null;
324 const ticketMatches = this.state.realWallet.matchWith({
325 Just: wal => wal.value.ticket === this.state.verifyTicket,
326 Nothing: () => false,
327 });
329 const continueReady =
330 stage === INVITE_STAGES.INVITE_WALLET
331 ? walletStates.includes(WALLET_STATES.DOWNLOADED)
332 : stage === INVITE_STAGES.INVITE_VERIFY
333 ? ticketMatches
334 : true;
336 const buttonElem =
337 stage === INVITE_STAGES.INVITE_TRANSACTIONS ? null : (
338 <Button
339 className={'mt-4'}
340 prop-size={'lg'}
341 prop-color={btnColor}
342 disabled={!continueReady}
343 onClick={clickHandler}>
344 {'Continue →'}
345 </Button>
346 );
348 return buttonElem;
349 }
351 getStageDisplay() {
352 const { stage } = this.state;
354 if (stage === INVITE_STAGES.INVITE_LOGIN) return null;
356 return (
357 <div className="mt-6 mb-8">
358 <span
359 className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_WALLET &&
360 'gray-20'}`}>
361 1 Passport
362 </span>
363 <span
364 className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_VERIFY &&
365 'gray-20'}`}>
366 2 Verify
367 </span>
368 <span
369 className={`fs-35 mr-3 ${stage !==
370 INVITE_STAGES.INVITE_TRANSACTIONS && 'gray-20'}`}>
371 3 Invite
372 </span>
373 </div>
374 );
375 }
377 getErrors() {
378 if (this.state.errors.length === 0) return null;
380 let errorElems = this.state.errors.map(e => <span>{e}</span>);
382 return (
383 <Warning>
384 <h3 className={'mb-2'}>{'Warning'}</h3>
385 {errorElems}
386 </Warning>
387 );
388 }
390 render() {
391 let stageDisplay = this.getStageDisplay();
392 let stageText = this.getStageText();
393 let ticketElem = this.getTicketElement();
394 let continueButton = this.getContinueButton();
395 let errorDisplay = this.getErrors();
397 return (
398 <View.Full>
399 <Grid>
400 <Grid.Item half={1}>
401 {stageDisplay}
402 {errorDisplay}
403 {stageText}
404 {ticketElem}
405 {continueButton}
407 {this.state.stage === INVITE_STAGES.INVITE_LOGIN && (
408 <Button
409 prop-type={`link`}
410 prop-size={`sm`}
411 className={`block mt-4`}
412 onClick={this.navigateLogin}>
413 {`Log in`}
414 </Button>
415 )}
416 </Grid.Item>
417 <Grid.Item half={2}>
418 <Passport
419 point={this.state.realPoint}
420 wallet={this.state.realWallet}
421 walletStates={this.state.walletStates}
422 pushWalletState={this.pushWalletState}
423 />
424 </Grid.Item>
425 </Grid>
426 </View.Full>
427 );
428 }
431export default compose(
432 withNetwork,
433 withHistory,
434 withWallet
435)(InviteTicket);