1 | import React from 'react'
|
2 | import ReactDOM from 'react-dom'
|
3 | import { Link } from 'react-router'
|
4 | import {ABCError} from 'airbitz-core-js'
|
5 |
|
6 | var strings = require('./abcui-strings')
|
7 |
|
8 | var AbcUiFormView = require('./abcui-formview.jsx')
|
9 | var LoginWithAirbitz = require('./abcui-loginwithairbitz.jsx')
|
10 | var classNames = require('classnames')
|
11 |
|
12 | var modal = require('./abcui-modal.jsx')
|
13 | var BootstrapButton = modal.BootstrapButton
|
14 | var BootstrapModal = modal.BootstrapModal
|
15 | var BootstrapInput = modal.BootstrapInput
|
16 |
|
17 | var PasswordRequirementsInput = require('./abcui-password.jsx')
|
18 |
|
19 | var context = window.parent.abcContext
|
20 |
|
21 | var AbcUserList = React.createClass({
|
22 | getInitialState () {
|
23 | return { showInput: false }
|
24 | },
|
25 | render () {
|
26 | var block = null
|
27 | var userList = context ? context.usernameList().sort() : []
|
28 | var toggleInput = null
|
29 | if (this.props.allowInput) {
|
30 | toggleInput = (
|
31 | <span className="input-group-btn">
|
32 | <button type="button" onClick={this.toggleInput} className="btn btn-primary">X</button>
|
33 | </span>)
|
34 | }
|
35 | if (this.props.allowInput && (userList.length === 0 || this.state.showInput)) {
|
36 | block = (
|
37 | <div className="input-group">
|
38 | <input autoFocus ref="username" type="text" placeholder="username" className="form-control" />
|
39 | <span className="input-group-btn">
|
40 | <button type="button" onClick={this.toggleInput} className="btn btn-primary">X</button>
|
41 | </span>
|
42 | </div>
|
43 | )
|
44 | } else {
|
45 | var selectElement = (
|
46 | <select ref="username"
|
47 | className="form-control"
|
48 | onChange={this.handleSelection}
|
49 | defaultValue={this.props.username}>
|
50 | {
|
51 | userList.map(function (username) {
|
52 | return (<option value={username} key={username}>{username}</option>)
|
53 | })
|
54 | }
|
55 | </select>
|
56 | )
|
57 | if (this.props.allowInput) {
|
58 | return (
|
59 | <div className="input-group">
|
60 | {selectElement}
|
61 | {toggleInput}
|
62 | </div>
|
63 | )
|
64 | } else {
|
65 | return selectElement
|
66 | }
|
67 | }
|
68 | return (block)
|
69 | },
|
70 | toggleInput () {
|
71 | this.setState({'showInput': !this.state.showInput})
|
72 | if (this.state.showInput) {
|
73 | this.setState({username: ''})
|
74 | this.refs.username.focus()
|
75 | }
|
76 | },
|
77 | handleSelection () {
|
78 | this.props.onUserChange(this.refs.username.value)
|
79 | },
|
80 | getValue () {
|
81 | return this.refs.username.value
|
82 | }
|
83 | })
|
84 |
|
85 | var AbcPasswordLoginForm = React.createClass({
|
86 | render () {
|
87 | return (
|
88 | <AbcUiFormView ref="form">
|
89 | <div className="row">
|
90 | <div className="col-sm-12">
|
91 | <div className="form-group">
|
92 | <AbcUserList
|
93 | ref="username"
|
94 | allowInput
|
95 | username={this.props.username}
|
96 | onUserChange={this.props.onUserChange} />
|
97 | </div>
|
98 | </div>
|
99 | </div>
|
100 | <div className="row">
|
101 | <div className="col-sm-12">
|
102 | <div className="form-group">
|
103 | <input ref="password" type="password" onKeyPress={this.handlePasswordKeyPress} placeholder={strings.password_text} className="form-control" />
|
104 | </div>
|
105 | </div>
|
106 | </div>
|
107 | <div className="row">
|
108 | <div className="col-sm-12 text-center">
|
109 | <div className="form-group">
|
110 | <BootstrapButton ref="signin" onClick={this.handleSubmit}>Sign In</BootstrapButton>
|
111 | </div>
|
112 | </div>
|
113 | </div>
|
114 | <div className="row">
|
115 | <div className="col-sm-12 text-center">
|
116 | <div className="form-group">
|
117 | <Link className="btn btn-link" to={'/recovery'}>Forgot Password</Link>
|
118 | </div>
|
119 | </div>
|
120 | </div>
|
121 | </AbcUiFormView>
|
122 | )
|
123 | },
|
124 | onClose () {
|
125 | },
|
126 | handlePasswordKeyPress (e) {
|
127 | if (e.key === 'Enter') {
|
128 | this.handleSubmit()
|
129 | }
|
130 | },
|
131 | handleSubmit () {
|
132 | var that = this
|
133 | this.refs.signin.setLoading(true)
|
134 | this.refs.form.setState({'error': null})
|
135 | context.loginWithPassword(this.refs.username.getValue(), this.refs.password.value, null, null, function (err, result) {
|
136 | if (err) {
|
137 | that.refs.form.setState({'error': ABCError(err, strings.invalid_password_text).message})
|
138 | } else {
|
139 | that.props.onSuccess(result)
|
140 | }
|
141 | that.refs.signin.setLoading(false)
|
142 | })
|
143 | return false
|
144 | }
|
145 | })
|
146 |
|
147 | var AbcPinLoginForm = React.createClass({
|
148 | render () {
|
149 | return (
|
150 | <AbcUiFormView ref="form">
|
151 | <div className="row">
|
152 | <div className="col-sm-12 text-center">
|
153 | <div className="form-group center-block" style={{'width': '240px'}}>
|
154 | <AbcUserList ref="username"
|
155 | allowInput={false}
|
156 | username={this.props.username}
|
157 | onUserChange={this.props.onUserChange} />
|
158 | </div>
|
159 | </div>
|
160 | <div className="col-sm-12 text-center">
|
161 | <div className="form-group center-block" style={{'width': '100px'}}>
|
162 | <input ref="pin" type="password" placeholder={strings.pin_text} onKeyPress={this.handlePinKeyPress} className="form-control" maxLength="4" />
|
163 | </div>
|
164 | </div>
|
165 | </div>
|
166 | <div className="row">
|
167 | <div className="col-sm-12 text-center">
|
168 | <div className="form-group center-block" style={{'width': '240px'}}>
|
169 | <BootstrapButton ref="signin" onClick={this.handleSubmit}>{strings.sign_in_text}</BootstrapButton>
|
170 | </div>
|
171 | </div>
|
172 | </div>
|
173 | <div className="row">
|
174 | <div className="col-sm-12 text-center">
|
175 | <div className="form-group center-block" style={{'width': '240px'}}>
|
176 | <button type="button" onClick={this.handleExit} className="btn-link">{strings.exit_pin_login_text}</button>
|
177 | </div>
|
178 | </div>
|
179 | </div>
|
180 | </AbcUiFormView>
|
181 | )
|
182 | },
|
183 | onClose () {
|
184 | },
|
185 | handleExit () {
|
186 | this.props.onExit()
|
187 | return false
|
188 | },
|
189 | handlePinKeyPress (e) {
|
190 | if (e.key === 'Enter') {
|
191 | this.handleSubmit()
|
192 | }
|
193 | },
|
194 | handleSubmit () {
|
195 | var that = this
|
196 | this.refs.signin.setLoading(true)
|
197 | context.loginWithPIN(this.refs.username.getValue(), this.refs.pin.value, function (err, result) {
|
198 | if (err) {
|
199 | that.refs.form.setState({'error': ABCError(err, 'Failed to login with PIN.').message})
|
200 | } else {
|
201 | that.props.onSuccess(result)
|
202 | }
|
203 | that.refs.signin.setLoading(false)
|
204 | })
|
205 | return false
|
206 | }
|
207 | })
|
208 |
|
209 | var LoginView = React.createClass({
|
210 | getInitialState () {
|
211 | var doRegistration = context.usernameList().sort().length === 0
|
212 | return {
|
213 | forcePasswordLogin: false,
|
214 | showRegistration: doRegistration
|
215 | }
|
216 | },
|
217 | statics: {
|
218 | currentUser () {
|
219 | return window.localStorage.getItem('airbitz.current_user')
|
220 | },
|
221 | updateCurrentUser (username) {
|
222 | window.localStorage.setItem('airbitz.current_user', username)
|
223 | }
|
224 | },
|
225 | render () {
|
226 | var block = null
|
227 | var currentUser = LoginView.currentUser()
|
228 | var showPinLogin = context && currentUser && context.pinExists(currentUser)
|
229 | if (this.state.forcePasswordLogin) {
|
230 | showPinLogin = false
|
231 | }
|
232 | if (this.state.showRegistration) {
|
233 | block = (
|
234 | <div>
|
235 | <RegistrationForm />
|
236 | <button type="button" onClick={this.handleSignIn} className="btn-link">{strings.sign_in_text}</button>
|
237 | </div>
|
238 | )
|
239 | } else if (showPinLogin) {
|
240 | block = (
|
241 | <div>
|
242 | <AbcPinLoginForm ref="pinPasswordForm"
|
243 | username={currentUser}
|
244 | onSuccess={this.handleSuccess}
|
245 | onError={this.handleError}
|
246 | onUserChange={this.handleUserChange}
|
247 | onExit={this.handlePinExit} />
|
248 | <button type="button" onClick={this.handleSignUp} className="btn-link">{strings.sign_up_text}</button>
|
249 | </div>
|
250 | )
|
251 | } else {
|
252 | block = (
|
253 | <div>
|
254 | <AbcPasswordLoginForm ref="pinPasswordForm"
|
255 | username={currentUser}
|
256 | onSuccess={this.handleSuccess}
|
257 | onError={this.handleError}
|
258 | onUserChange={this.handleUserChange} />
|
259 | <button type="button" onClick={this.handleSignUp} className="btn-link">{strings.sign_up_text}</button>
|
260 | </div>
|
261 | )
|
262 | }
|
263 | return (
|
264 | <BootstrapModal
|
265 | ref="loginModal"
|
266 | key="loginModal"
|
267 | cancel="Cancel"
|
268 | title="Airbitz Edge Login"
|
269 | onClose={this.onClose}>
|
270 | <LoginWithAirbitz onLogin={this.handleSuccess} ref="loginWithAirbitz" />
|
271 | {block}
|
272 | </BootstrapModal>
|
273 | )
|
274 | },
|
275 | handlePinExit () {
|
276 | this.setState({'forcePasswordLogin': true})
|
277 | },
|
278 | handleUserChange (newUsername) {
|
279 | LoginView.updateCurrentUser(newUsername)
|
280 | this.setState({'forcePasswordLogin': false})
|
281 | },
|
282 | handleSuccess (account) {
|
283 | this.refs.loginWithAirbitz.cancelRequest()
|
284 | this.setState({'forcePasswordLogin': false})
|
285 | LoginView.updateCurrentUser(account.username)
|
286 | if (window.parent.loginCallback) {
|
287 | this.refs.loginModal.close()
|
288 | window.parent.loginCallback(null, account)
|
289 | }
|
290 | },
|
291 | handleSignIn () {
|
292 | this.setState({showRegistration: false})
|
293 | },
|
294 | handleSignUp () {
|
295 | this.setState({showRegistration: true})
|
296 | },
|
297 | onClose () {
|
298 | this.refs.loginWithAirbitz.cancelRequest()
|
299 | if (this.refs.pinPasswordForm) {
|
300 | this.refs.pinPasswordForm.onClose()
|
301 | }
|
302 | if (window.parent.exitCallback) {
|
303 | window.parent.exitCallback()
|
304 | }
|
305 | }
|
306 | })
|
307 |
|
308 | var RegistrationForm = React.createClass({
|
309 | getInitialState () {
|
310 | return {
|
311 | showSuccess: false,
|
312 | account: null,
|
313 | usernameError: false
|
314 | }
|
315 | },
|
316 | render () {
|
317 | var usernameClass = classNames({
|
318 | 'form-group': true,
|
319 | 'has-error': this.state.usernameError
|
320 | })
|
321 | var regForm = (
|
322 | <AbcUiFormView ref="form">
|
323 | <div className="row">
|
324 | <div className="col-sm-12">
|
325 | <div className={usernameClass}>
|
326 | <BootstrapInput type="text" ref="username" onKeyPress={this.handleKeypressUsername} placeholder="Choose a Username" className="form-control" onBlur={this.blur} onFocus={this.focus} />
|
327 | </div>
|
328 | </div>
|
329 | <div className="col-sm-12">
|
330 | <div className="form-group">
|
331 | <PasswordRequirementsInput ref="password" onKeyPress={this.handleKeypressPassword} placeholder="Choose a Password" className="form-control" />
|
332 | </div>
|
333 | </div>
|
334 | <div className="col-sm-12">
|
335 | <div className="form-group">
|
336 | <PasswordRequirementsInput ref="password_repeat" onKeyPress={this.handleKeypressPassword2} placeholder="Repeat Password" className="form-control" />
|
337 | </div>
|
338 | </div>
|
339 |
|
340 | <div className="col-sm-12">
|
341 | <div className="form-group">
|
342 | <div className="input-group">
|
343 | <input type="password" ref="pin" onKeyPress={this.handleKeypressPin} maxLength="4" placeholder="Choose a 4 Digit PIN" className="form-control" />
|
344 | </div>
|
345 | </div>
|
346 | </div>
|
347 | <div className="col-sm-12">
|
348 | <div className="form-group">
|
349 | <span className="input-group-btn">
|
350 | <BootstrapButton ref="register" onClick={this.handleSubmit}>Register</BootstrapButton>
|
351 | </span>
|
352 | </div>
|
353 | </div>
|
354 | </div>
|
355 | </AbcUiFormView>
|
356 | )
|
357 |
|
358 | var successMessage = (
|
359 | <div>
|
360 | <BootstrapModal ref="regModal" title={strings.account_created_text} onClose={this.onClose}>
|
361 | {String.format(strings.account_created_message, window.parent.abcuiContext.vendorName)}
|
362 | <br /><br />
|
363 | {String.format(strings.account_created_zero_knowledge, window.parent.abcuiContext.vendorName)}
|
364 | <br /><br />
|
365 | {String.format(strings.account_created_write_it_down, window.parent.abcuiContext.vendorName)}
|
366 | <br /><br />
|
367 | <span className="input-group-btn">
|
368 | <BootstrapButton onClick={this.onSuccessSetupRecovery}>{strings.setup_recovery_text}</BootstrapButton>
|
369 | </span>
|
370 | <span className="input-group-btn">
|
371 | <BootstrapButton onClick={this.onSuccessClose}>{strings.later_button_text}</BootstrapButton>
|
372 | </span>
|
373 | </BootstrapModal>
|
374 | </div>
|
375 | )
|
376 |
|
377 | if (this.state.showSuccess) {
|
378 | return successMessage
|
379 | } else {
|
380 | return regForm
|
381 | }
|
382 | },
|
383 | focus () {
|
384 | this.refs.username.setState({error: null, loading: null})
|
385 | },
|
386 | blur () {
|
387 | var that = this
|
388 | var username = that.refs.username.value()
|
389 | if (username) {
|
390 | that.refs.username.setState({error: null, loading: 'Checking availability...'})
|
391 | context.usernameAvailable(username, function (err) {
|
392 | if (err) {
|
393 | that.setState({usernameError: true})
|
394 | that.refs.username.setState({error: strings.username_already_taken, loading: null})
|
395 | } else {
|
396 | that.setState({usernameError: false})
|
397 | that.refs.username.setState({error: null, loading: null})
|
398 | }
|
399 | })
|
400 | } else {
|
401 | that.refs.username.setState({error: null})
|
402 | }
|
403 | },
|
404 | handleKeypressUsername (e) {
|
405 | if (e.key === 'Enter') {
|
406 | this.refs.password.setFocus()
|
407 | }
|
408 | },
|
409 | handleKeypressPassword (e) {
|
410 | if (e.key === 'Enter') {
|
411 | this.refs.password_repeat.setFocus()
|
412 | }
|
413 | },
|
414 | handleKeypressPassword2 (e) {
|
415 | if (e.key === 'Enter') {
|
416 | ReactDOM.findDOMNode(this.refs.pin).focus()
|
417 | }
|
418 | },
|
419 | handleKeypressPin (e) {
|
420 | if (e.key === 'Enter') {
|
421 | this.handleSubmit()
|
422 | }
|
423 | },
|
424 | handleSubmit () {
|
425 | var that = this
|
426 | if (this.refs.password.value() !== this.refs.password_repeat.value()) {
|
427 | that.refs.form.setState({ 'error': 'Passwords do not match' })
|
428 | return false
|
429 | }
|
430 | var checkPasswdResults = context.checkPasswordRules(this.refs.password.value())
|
431 | if (!checkPasswdResults.passed) {
|
432 | that.refs.form.setState({ 'error': 'Insufficient Password' })
|
433 | return false
|
434 | }
|
435 | if (this.refs.pin.value.length !== 4) {
|
436 | that.refs.form.setState({ 'error': 'PIN Must be 4 digits long' })
|
437 | return false
|
438 | }
|
439 | var onlyNumbers = /^\d+$/.test(that.refs.pin.value)
|
440 | if (!onlyNumbers) {
|
441 | that.refs.form.setState({ 'error': 'PIN must only have numbers' })
|
442 | return false
|
443 | }
|
444 |
|
445 | this.refs.register.setLoading(true)
|
446 | var username = this.refs.username.value()
|
447 | context.createAccount(username, this.refs.password.value(), this.refs.pin.value, function (err, result) {
|
448 | that.refs.register.setLoading(false)
|
449 | if (err) {
|
450 | that.refs.form.setState({'error': ABCError(err, 'Unable to register at this time.').message})
|
451 | } else {
|
452 | var account = result
|
453 | LoginView.updateCurrentUser(account.username)
|
454 | that.setState({account: account})
|
455 | that.setState({showSuccess: true})
|
456 | }
|
457 | })
|
458 | return false
|
459 | },
|
460 | onLogin (account) {
|
461 | LoginView.updateCurrentUser(account.username)
|
462 |
|
463 |
|
464 | if (window.parent.loginCallback) {
|
465 | window.parent.loginCallback(null, account)
|
466 | }
|
467 | this.refs.regModal.close()
|
468 | this.refs.register.setLoading(false)
|
469 |
|
470 | },
|
471 | onClose () {
|
472 | if (window.parent.exitCallback) {
|
473 | window.parent.exitCallback()
|
474 | }
|
475 | },
|
476 | onSuccessClose () {
|
477 | if (window.parent.loginCallback) {
|
478 | window.parent.loginCallback(null, this.state.account)
|
479 | }
|
480 | },
|
481 | onSuccessSetupRecovery () {
|
482 | if (window.parent.loginCallback) {
|
483 | window.parent.loginCallback(null, this.state.account, {setupRecovery: true})
|
484 | }
|
485 | }
|
486 | })
|
487 |
|
488 | module.exports = LoginView
|