@@ -1,13 +1,16 @@
11import { Just, Nothing } from 'folktale/maybe'
22import React from 'react'
3−import { Button } from '../components/Base'
4−import { InnerLabel, ValidatedSigil, PointInput, TicketInput } from '../components/Base'
5−import { Row, Col, H1, P } from '../components/Base'
3+import { InnerLabel, ValidatedSigil, PointInput,
4+ TicketInput, Button, Row, Col, H1, P, Passport } from '../components/Base'
65import * as kg from '../../../node_modules/urbit-key-generation/dist/index'
76import * as ob from 'urbit-ob'
7+import * as azimuth from 'azimuth-js'
88
99import { ROUTE_NAMES } from '../lib/router'
10−import { DEFAULT_HD_PATH, urbitWalletFromTicket } from '../lib/wallet'
10+import { DEFAULT_HD_PATH, urbitWalletFromTicket,
11+ walletFromMnemonic, addressFromSecp256k1Public } from '../lib/wallet'
12+import { BRIDGE_ERROR } from '../lib/error'
13+import { generateWallet } from '../lib/invite'
1114
1215const placeholder = (len) => {
1316 let bytes = window.crypto.getRandomValues(new Uint8Array(len))
@@ -18,21 +21,34 @@ const placeholder = (len) => {
1821 return ob.hex2patq(hex)
1922}
2023
24+const INVITE_STAGES = {
25+ INVITE_LOGIN: "invite login",
26+ INVITE_WALLET: "invite wallet",
27+ INVITE_VERIFY: "invite verify",
28+ INVITE_TRANSACTIONS: "invite transactions"
29+}
30+
2131class InviteLogin extends React.Component {
2232
2333 constructor(props) {
2434 super(props)
2535
2636 this.state = {
27− ticket: '',
28− pointName: '',
29− isUnlocking: false
37+ inviteTicket: '',
38+ verifyTicket: '',
39+ realPoint: Nothing(),
40+ realWallet: Nothing(),
41+ walletReady: false,
42+ continueReady: true,
43+ stage: INVITE_STAGES.INVITE_LOGIN
3044 }
3145
3246 this.pointPlaceholder = placeholder(4)
3347 this.ticketPlaceholder = placeholder(12)
3448
35− this.handleTicketInput = this.handleTicketInput.bind(this)
49+ this.handleInviteTicketInput = this.handleInviteTicketInput.bind(this)
50+ this.handleVerifyTicketInput = this.handleVerifyTicketInput.bind(this)
51+ this.confirmWalletDownload = this.confirmWalletDownload.bind(this)
3652 this.navigateLogin = this.navigateLogin.bind(this)
3753 }
3854
@@ -41,72 +57,195 @@ class InviteLogin extends React.Component {
4157 this.props.pushRoute(ROUTE_NAMES.NETWORK)
4258 }
4359
44− handleTicketInput(ticket) {
45− this.setState({ ticket })
60+ handleInviteTicketInput(inviteTicket) {
61+ this.setState({ inviteTicket })
4662 }
4763
48− async walletFromTicket(ticket, pointName) {
64+ handleVerifyTicketInput(verifyTicket) {
65+ this.setState({ verifyTicket })
66+ }
67+
68+ confirmWalletDownload() {
4969 this.setState({
50− isUnlocking: true
70+ continueReady: true
71+ })
72+ }
73+
74+ async unlockInviteWallet(inviteTicket) {
75+ this.setState({
76+ continueReady: false,
77+ stage: INVITE_STAGES.INVITE_WALLET
78+ });
79+
80+ const uhdw = await urbitWalletFromTicket(inviteTicket, '~zod');
81+ const mnemonic = uhdw.ownership.seed;
82+ const inviteWallet = walletFromMnemonic(
83+ mnemonic,
84+ DEFAULT_HD_PATH,
85+ uhdw.meta.passphrase
86+ ).matchWith({
87+ Just: w => w.value,
88+ Nothing: () => null
5189 });
5290
53− const uhdw = await urbitWalletFromTicket(ticket, '~zod');
54− this.props.setUrbitWallet(Just(uhdw));
91+ const addr = addressFromSecp256k1Public(inviteWallet.publicKey);
92+
93+ const ctrcs = this.props.contracts.matchWith({
94+ Just: ctrcs => ctrcs.value,
95+ Nothing: () => {
96+ throw BRIDGE_ERROR.MISSING_CONTRACTS
97+ }
98+ })
99+
100+ const incoming = await azimuth.azimuth.getTransferringFor(ctrcs, addr)
101+
102+ let point
103+ let wallet
104+
105+ if (incoming.length > 0) {
106+ let pointNum = parseInt(incoming[0]);
107+ point = Just(pointNum);
108+ wallet = await generateWallet(pointNum);
109+ if (incoming.length > 1) {
110+ //TODO notify user "...and others / ticket reusable"
111+ }
112+ }
55113
56114 this.setState({
57− isUnlocking: false
58− });
59− this.props.pushRoute(ROUTE_NAMES.INVITE_WALLET);
115+ realPoint: point,
116+ realWallet: Just(wallet)
117+ })
118+ }
119+
120+ getTicketElement() {
121+ const phTick = this.ticketPlaceholder
122+
123+ let isLogin = this.state.stage === INVITE_STAGES.INVITE_LOGIN
124+ let isVerify = this.state.stage === INVITE_STAGES.INVITE_VERIFY
125+
126+ if (isLogin || isVerify) {
127+
128+ let value = isLogin ? this.state.inviteTicket : this.state.verifyTicket
129+ let handler = isLogin ? this.handleInviteTicketInput : this.handleVerifyTicketInput
130+ let label = isLogin ? 'Activation code' : 'ticket'
131+
132+ return (
133+ <TicketInput
134+ className='mono mt-8'
135+ prop-size='md'
136+ prop-format='innerLabel'
137+ type='text'
138+ autoFocus
139+ name='invite'
140+ placeholder={ `e.g. ${phTick}` }
141+ value={ value }
142+ onChange={ handler }>
143+ <InnerLabel>{ label }</InnerLabel>
144+ </TicketInput>
145+ )
146+ } else {
147+ return null
148+ }
149+ }
150+
151+ getStageText() {
152+ switch (this.state.stage) {
153+ case INVITE_STAGES.INVITE_LOGIN:
154+ return (
155+ <h1 className="text-700">Welcome</h1>
156+ )
157+ break
158+ case INVITE_STAGES.INVITE_WALLET:
159+ return (
160+ <div>
161+ <h1 className="fs-6 lh-8">Welcome</h1>
162+ <p>A passport is your digital identity. You will use your passport to access your true computer, send payments, and administer your identity. So naturally, you must keep this secure.</p>
163+ <p>After you’ve downloaded your passport, back up the ticket manually or store on a trusted device.</p>
164+ </div>
165+ )
166+ break
167+ }
168+ }
169+
170+ getContinueButton() {
171+ if (this.state.stage === INVITE_STAGES.INVITE_TRANSACTIONS) return null
172+
173+ let clickHandler
174+ let btnColor
175+
176+ switch(this.state.stage) {
177+ case INVITE_STAGES.INVITE_LOGIN:
178+ clickHandler = () => {
179+ this.unlockInviteWallet(this.state.inviteTicket)
180+ }
181+ btnColor = "black"
182+ break
183+ case INVITE_STAGES.INVITE_VERIFY:
184+ case INVITE_STAGES.INVITE_WALLET:
185+ btnColor = "green"
186+ break
187+ }
188+
189+ return (
190+ <Button
191+ className={'mt-4'}
192+ prop-size={'md'}
193+ prop-color={btnColor}
194+ disabled={!this.state.continueReady}
195+ onClick={clickHandler}
196+ >
197+ { 'Continue →' }
198+ </Button>
199+ )
200+ }
201+
202+ getStageDisplay() {
203+ const { stage } = this.state
204+
205+ return (
206+ <div className="mt-6 mb-8">
207+ <span className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_WALLET && 'gray-20'}`}>1 Passport</span>
208+ <span className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_VERIFY && 'gray-20'}`}>2 Verify</span>
209+ <span className={`fs-35 mr-3 ${stage !== INVITE_STAGES.INVITE_TRANSACTIONS && 'gray-20'}`}>3 Invite</span>
210+ </div>
211+ )
60212 }
61213
62214 render() {
63215 const { wallet } = this.props
64− const { ticket, pointName } = this.state
216+ const { inviteTicket } = this.state
65217
66− const phTick = this.ticketPlaceholder
218+ let stageDisplay = this.getStageDisplay()
219+ let stageText = this.getStageText()
220+ let ticketElem = this.getTicketElement()
221+ let continueButton = this.getContinueButton()
67222
68223 return (
69224 <Row>
70− <Col className="col-md-4">
71− <H1>{ 'Welcome' }</H1>
72−
73− <TicketInput
74− className='mono mt-8'
75− prop-size='md'
76− prop-format='innerLabel'
77− type='text'
78− autoFocus
79− name='invite'
80− placeholder={ `e.g. ${phTick}` }
81− value={ ticket }
82− onChange={ this.handleTicketInput }>
83− <InnerLabel>{ 'Activation code' }</InnerLabel>
84− </TicketInput>
85−
86− <Button
87− className={'mt-8'}
88− prop-size={'lg'}
89− disabled={this.state.isUnlocking}
90− prop-color={'black'}
91− onClick={() => this.walletFromTicket(ticket, pointName)}>
92−
93− <span className="relative">
94− {this.state.isUnlocking &&
95− <span className="btn-spinner"></span>
96− }
97− {'Continue →'}
98− </span>
99− </Button>
100−
101− <Button
102− prop-type={`link`}
103− prop-size={`sm`}
104− className={`block mt-4`}
105− onClick={this.navigateLogin} >
106− {`Log in`}
107− </Button>
225+ <Col className="col-md-4 mt-">
226+ {stageDisplay}
227+ {stageText}
228+ {ticketElem}
229+ {continueButton}
230+
231+ { this.state.stage === INVITE_STAGES.INVITE_LOGIN &&
232+ <Button
233+ prop-type={`link`}
234+ prop-size={`sm`}
235+ className={`block mt-4`}
236+ onClick={this.navigateLogin} >
237+ {`Log in`}
238+ </Button>
239+ }
108240
109241 </Col>
242+ <Col className="col-md-offset-3 col-md-5 mt-18">
243+ <Passport
244+ point={this.state.realPoint}
245+ wallet={this.state.realWallet}
246+ confirmWalletDownload={this.confirmWalletDownload}
247+ />
248+ </Col>
110249 </Row>
111250 )
112251 }