on 04765bb help sign in
bridge/src/views/InviteTicket.js
434 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 'indigo-react';
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.popAndPush(ROUTE_NAMES.LOGIN);
72 }
74 handleInviteTicketInput(inviteTicket) {
75 this.setState({ inviteTicket });
76 }
78 handleVerifyTicketInput(verifyTicket) {
79 this.setState({ verifyTicket });
80 }
82 updateTransactionProgress(transactionProgress) {
83 this.setState({ transactionProgress });
84 }
86 async unlockInviteWallet(inviteTicket) {
87 this.setState({
88 walletStates: this.state.walletStates.concat(WALLET_STATES.UNLOCKING),
89 stage: INVITE_STAGES.INVITE_WALLET,
90 });
92 //NOTE ~zod because tmp wallet
93 const ownership = await ownershipWalletFromTicket(inviteTicket, 0);
94 const mnemonic = ownership.seed;
95 //TODO isn't all this accessible in the ownership object?
96 const inviteWallet = walletFromMnemonic(
97 mnemonic,
98 DEFAULT_HD_PATH
99 ).matchWith({
100 Just: w => w.value,
101 Nothing: () => {
102 throw BRIDGE_ERROR.MISSING_WALLET;
103 },
104 });
106 const addr = inviteWallet.address;
108 const contracts = need.contracts(this.props.contracts);
110 const incoming = await azimuth.azimuth.getTransferringFor(contracts, addr);
112 this.setState({
113 walletStates: this.state.walletStates.concat(WALLET_STATES.GENERATING),
114 });
116 let realPoint = Nothing();
117 let realWallet = Nothing();
118 let errors = [];
120 if (incoming.length > 0) {
121 let pointNum = parseInt(incoming[0], 10);
122 realPoint = Just(pointNum);
124 this.setState({
125 realPoint,
126 });
128 let genWallet = await generateWallet(pointNum);
130 realWallet = Just(genWallet);
131 if (incoming.length > 1) {
132 //TODO notify user "...and others / ticket reusable"
133 }
134 } else {
135 errors = [
136 'Invite code has no claimable ship. Check your invite code and try again.',
137 ];
138 }
140 this.setState({
141 realWallet,
142 inviteWallet: Just(inviteWallet),
143 // dev mode: uncomment this out to autofill verified ticket
144 verifyTicket: !Nothing.hasInstance(realWallet)
145 ? realWallet.value.ticket
146 : '',
147 walletStates: this.state.walletStates.concat(WALLET_STATES.RENDERING),
148 errors,
149 });
150 }
152 getTicketElement() {
153 const phTick = this.ticketPlaceholder;
155 let isLogin = this.state.stage === INVITE_STAGES.INVITE_LOGIN;
156 let isVerify = this.state.stage === INVITE_STAGES.INVITE_VERIFY;
158 if (isLogin || isVerify) {
159 let value = isLogin ? this.state.inviteTicket : this.state.verifyTicket;
160 let handler = isLogin
161 ? this.handleInviteTicketInput
162 : this.handleVerifyTicketInput;
163 let label = isLogin ? 'Activation code' : 'ticket';
165 // if realWallet is Just, then VerifyTicketInput appropriate contents; if Nothing, remain Nothing
166 const Verified = this.state.realWallet.map(realWallet =>
167 VerifyTicketInput(realWallet.ticket)
168 );
170 // Verified.getOrElse(TicketInput) falls back to TicketInput if Verified is Nothing
171 const TicketElem = isVerify
172 ? Verified.getOrElse(TicketInput)
173 : TicketInput;
175 return (
176 <TicketElem
177 className="mono mt-8"
178 prop-size="md"
179 prop-format="innerLabel"
180 type="text"
181 autoFocus
182 name="invite"
183 placeholder={`e.g. ${phTick}`}
184 value={value}
185 onChange={handler}>
186 <InnerLabel>{label}</InnerLabel>
187 </TicketElem>
188 );
189 } else {
190 return null;
191 }
192 }
194 getStageText() {
195 const { stage } = this.state;
197 let progressWidth = this.state.transactionProgress.pct;
199 const stageText =
200 stage === INVITE_STAGES.INVITE_LOGIN ? (
201 <h1 className="text-700 mt-15">Welcome</h1>
202 ) : stage === INVITE_STAGES.INVITE_WALLET ? (
203 <div>
204 <h1 className="fs-6 lh-8 mb-3">Passport</h1>
205 <p>
206 A passport is your digital identity. You will use your passport to
207 access your true computer, send payments, and administer your
208 identity. So naturally, you must keep this secure.
209 </p>
210 <p>
211 After you’ve downloaded your passport, back up the ticket manually
212 or store on a trusted device.
213 </p>
214 </div>
215 ) : stage === INVITE_STAGES.INVITE_VERIFY ? (
216 <div>
217 <h1 className="fs-6 lh-8 mb-3">Verify Passport</h1>
218 <p>
219 Afer you download your passport, verify your custody. Your passport
220 should be a folder of image files. One of them is your Master
221 Ticket. Open it and enter the 4 word phrase below (with hyphens).
222 </p>
223 </div>
224 ) : INVITE_STAGES.INVITE_TRANSACTIONS ? (
225 this.state.transactionProgress.label ===
226 TRANSACTION_STATES.DONE.label ? (
227 <div>
228 <h1 className="fs-6 lh-8 mb-3">
229 <span>Success</span>
230 <span className="ml-4 green">✓</span>
231 </h1>
232 <p>Transferring to Bridge...</p>
233 </div>
234 ) : (
235 <div>
236 <h1 className="fs-6 lh-8 mb-3">Submitting</h1>
237 <p className="mt-4 mb-4">
238 This step can take up to five minutes. Please do not leave this
239 page until the transactions are complete.
240 </p>
241 <div className="passport-progress mb-2">
242 <div
243 className="passport-progress-filled"
244 style={{ width: progressWidth }}
245 />
246 </div>
247 <div className="flex justify-between">
248 <div className="text-mono text-sm lh-6 green-dark">
249 {this.state.transactionProgress.label}
250 </div>
251 </div>
252 </div>
253 )
254 ) : null;
256 return stageText;
257 }
259 updateProgress(notification) {
260 let newState =
261 notification.type === 'progress'
262 ? { transactionProgress: notification.value }
263 : notification.type === 'notify'
264 ? { errors: [notification.value] }
265 : {};
267 this.setState(newState);
268 }
270 getContinueButton() {
271 const {
272 realPoint,
273 realWallet,
274 inviteWallet,
275 stage,
276 walletStates,
277 verifyTicket,
278 inviteTicket,
279 } = this.state;
280 const { web3, contracts, setUrbitWallet } = this.props;
282 const btnColor = this.state.walletStates.includes(WALLET_STATES.PAPER_READY)
283 ? 'green'
284 : 'black';
286 const clickHandler =
287 stage === INVITE_STAGES.INVITE_LOGIN
288 ? () => {
289 this.unlockInviteWallet(inviteTicket);
290 }
291 : stage === INVITE_STAGES.INVITE_WALLET
292 ? () => {
293 this.setState({
294 stage: INVITE_STAGES.INVITE_VERIFY,
295 });
296 }
297 : stage === INVITE_STAGES.INVITE_VERIFY
298 ? () => {
299 this.setState({
300 stage: INVITE_STAGES.INVITE_TRANSACTIONS,
301 walletStates: this.state.walletStates.concat(
302 WALLET_STATES.TRANSACTIONS
303 ),
304 });
306 startTransactions({
307 inviteWalletM: inviteWallet,
308 realWalletM: realWallet,
309 realPointM: realPoint,
310 realTicket: verifyTicket,
311 web3,
312 contracts,
313 setUrbitWallet,
314 updateProgress: this.updateProgress,
315 }).then(() => {
316 setTimeout(() => {
317 this.props.history.pop();
318 }, 5000);
319 });
320 }
321 : null;
323 const ticketMatches = this.state.realWallet.matchWith({
324 Just: wal => wal.value.ticket === this.state.verifyTicket,
325 Nothing: () => false,
326 });
328 const continueReady =
329 stage === INVITE_STAGES.INVITE_WALLET
330 ? walletStates.includes(WALLET_STATES.DOWNLOADED)
331 : stage === INVITE_STAGES.INVITE_VERIFY
332 ? ticketMatches
333 : true;
335 const buttonElem =
336 stage === INVITE_STAGES.INVITE_TRANSACTIONS ? null : (
337 <Button
338 className={'mt-4'}
339 prop-size={'lg'}
340 prop-color={btnColor}
341 disabled={!continueReady}
342 onClick={clickHandler}>
343 {'Continue →'}
344 </Button>
345 );
347 return buttonElem;
348 }
350 getStageDisplay() {
351 const { stage } = this.state;
353 if (stage === INVITE_STAGES.INVITE_LOGIN) return null;
355 return (
356 <div className="mt-6 mb-8">
357 <span
358 className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_WALLET &&
359 'gray-20'}`}>
360 1 Passport
361 </span>
362 <span
363 className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_VERIFY &&
364 'gray-20'}`}>
365 2 Verify
366 </span>
367 <span
368 className={`fs-35 mr-3 ${stage !==
369 INVITE_STAGES.INVITE_TRANSACTIONS && 'gray-20'}`}>
370 3 Invite
371 </span>
372 </div>
373 );
374 }
376 getErrors() {
377 if (this.state.errors.length === 0) return null;
379 let errorElems = this.state.errors.map(e => <span>{e}</span>);
381 return (
382 <Warning>
383 <h3 className={'mb-2'}>{'Warning'}</h3>
384 {errorElems}
385 </Warning>
386 );
387 }
389 render() {
390 let stageDisplay = this.getStageDisplay();
391 let stageText = this.getStageText();
392 let ticketElem = this.getTicketElement();
393 let continueButton = this.getContinueButton();
394 let errorDisplay = this.getErrors();
396 return (
397 <View.Full>
398 <Grid>
399 <Grid.Item half={1}>
400 {stageDisplay}
401 {errorDisplay}
402 {stageText}
403 {ticketElem}
404 {continueButton}
406 {this.state.stage === INVITE_STAGES.INVITE_LOGIN && (
407 <Button
408 prop-type={`link`}
409 prop-size={`sm`}
410 className={`block mt-4`}
411 onClick={this.navigateLogin}>
412 {`Log in`}
413 </Button>
414 )}
415 </Grid.Item>
416 <Grid.Item half={2}>
417 <Passport
418 point={this.state.realPoint}
419 wallet={this.state.realWallet}
420 walletStates={this.state.walletStates}
421 pushWalletState={this.pushWalletState}
422 />
423 </Grid.Item>
424 </Grid>
425 </View.Full>
426 );
427 }
430export default compose(
431 withNetwork,
432 withHistory,
433 withWallet
434)(InviteTicket);