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