2015-09-23 11:11:51 +08:00
|
|
|
React = require 'react'
|
|
|
|
|
|
|
|
ipc = require 'ipc'
|
|
|
|
{RetinaImg} = require 'nylas-component-kit'
|
|
|
|
{EdgehillAPI, NylasAPI, APIError} = require 'nylas-exports'
|
|
|
|
|
|
|
|
OnboardingActions = require './onboarding-actions'
|
|
|
|
NylasApiEnvironmentStore = require './nylas-api-environment-store'
|
|
|
|
Providers = require './account-types'
|
|
|
|
|
2015-09-25 05:51:15 +08:00
|
|
|
class AccountSettingsPage extends React.Component
|
2015-09-23 11:11:51 +08:00
|
|
|
@displayName: "AccountSettingsPage"
|
|
|
|
|
|
|
|
constructor: (@props) ->
|
|
|
|
@state =
|
|
|
|
provider: @props.pageData.provider
|
|
|
|
settings: {}
|
|
|
|
fields: {}
|
|
|
|
pageNumber: 0
|
2015-09-25 05:51:15 +08:00
|
|
|
errorFieldNames: []
|
|
|
|
errorMessage: null
|
2015-09-23 11:11:51 +08:00
|
|
|
show_advanced: false
|
|
|
|
|
|
|
|
@props.pageData.provider.settings.forEach (field) =>
|
|
|
|
if field.default?
|
|
|
|
@state.settings[field.name] = field.default
|
|
|
|
|
|
|
|
if @state.provider.name is 'gmail'
|
|
|
|
poll_attempt_id = 0
|
|
|
|
done = false
|
|
|
|
# polling with capped exponential backoff
|
|
|
|
delay = 1000
|
|
|
|
tries = 0
|
|
|
|
poll = (id,initial_delay) =>
|
|
|
|
_retry = =>
|
|
|
|
tries++
|
|
|
|
@_pollForGmailAccount((account) ->
|
|
|
|
if account?
|
|
|
|
done = true
|
2015-09-25 05:51:15 +08:00
|
|
|
OnboardingActions.accountJSONReceived(account)
|
2015-09-23 11:11:51 +08:00
|
|
|
else if tries < 10 and id is poll_attempt_id
|
|
|
|
setTimeout(_retry, delay)
|
|
|
|
delay *= 1.5 # exponential backoff
|
|
|
|
)
|
|
|
|
setTimeout(_retry,initial_delay)
|
|
|
|
|
|
|
|
ipc.on('browser-window-focus', ->
|
|
|
|
if not done # hack to deactivate this listener when done
|
|
|
|
poll_attempt_id++
|
|
|
|
poll(poll_attempt_id,0)
|
|
|
|
)
|
|
|
|
poll(poll_attempt_id,2000)
|
|
|
|
|
|
|
|
componentDidMount: ->
|
|
|
|
|
|
|
|
componentWillUnmount: ->
|
|
|
|
|
|
|
|
render: ->
|
|
|
|
<div className="page account-setup">
|
|
|
|
|
|
|
|
<div className="logo-container">
|
|
|
|
<RetinaImg name={@state.provider.header_icon} mode={RetinaImg.Mode.ContentPreserve} className="logo"/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@_renderTitle()}
|
|
|
|
|
|
|
|
<div className="back" onClick={@_fireMoveToPrevPage}>
|
|
|
|
<RetinaImg name="onboarding-back.png"
|
|
|
|
mode={RetinaImg.Mode.ContentPreserve}/>
|
|
|
|
</div>
|
|
|
|
{@_renderErrorMessage()}
|
|
|
|
<form className="settings">
|
|
|
|
{@_renderFields()}
|
|
|
|
{@_renderSettings()}
|
|
|
|
{@_renderButton()}
|
|
|
|
</form>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
_onSettingsChanged: (event) =>
|
|
|
|
field = event.target.dataset.field
|
2015-10-03 06:32:59 +08:00
|
|
|
format = event.target.dataset.format
|
|
|
|
int_formatter = (a) ->
|
|
|
|
i = parseInt(a)
|
|
|
|
if isNaN(i) then "" else i
|
|
|
|
formatter = if format is 'integer' then int_formatter else (a) -> a
|
2015-09-23 11:11:51 +08:00
|
|
|
settings = @state.settings
|
|
|
|
if event.target.type is 'checkbox'
|
|
|
|
settings[field] = event.target.checked
|
|
|
|
else
|
2015-10-03 06:32:59 +08:00
|
|
|
settings[field] = formatter(event.target.value)
|
2015-09-23 11:11:51 +08:00
|
|
|
@setState({settings})
|
|
|
|
|
|
|
|
_onValueChanged: (event) =>
|
|
|
|
field = event.target.dataset.field
|
|
|
|
fields = @state.fields
|
|
|
|
fields[field] = event.target.value
|
|
|
|
@setState({fields})
|
|
|
|
|
2015-10-01 02:58:40 +08:00
|
|
|
_onFieldKeyPress: (event) =>
|
|
|
|
if event.key in ['Enter', 'Return']
|
2015-10-03 06:32:59 +08:00
|
|
|
pages = @state.provider.pages || []
|
|
|
|
if pages.length > @state.pageNumber+1
|
|
|
|
@_onNextButton()
|
|
|
|
else
|
|
|
|
@_onSubmit()
|
2015-10-01 02:58:40 +08:00
|
|
|
|
2015-09-23 11:11:51 +08:00
|
|
|
_renderTitle: =>
|
|
|
|
if @state.provider.name is 'gmail'
|
|
|
|
<h2>
|
|
|
|
Sign in to {@state.provider.displayName} in your browser.
|
|
|
|
</h2>
|
|
|
|
else if @state.provider.pages?.length > 0
|
|
|
|
<h2>
|
|
|
|
{@state.provider.pages[@state.pageNumber]}
|
|
|
|
</h2>
|
|
|
|
else
|
|
|
|
<h2>
|
|
|
|
Sign in to {@state.provider.displayName}
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
_renderErrorMessage: =>
|
2015-09-25 05:51:15 +08:00
|
|
|
if @state.errorMessage
|
|
|
|
<div className="errormsg">{@state.errorMessage ? ""}</div>
|
2015-09-23 11:11:51 +08:00
|
|
|
|
|
|
|
_fieldOnCurrentPage: (field) =>
|
|
|
|
!@state.provider.pages || field.page is @state.pageNumber
|
|
|
|
|
|
|
|
_renderFields: =>
|
|
|
|
@state.provider.fields?.filter(@_fieldOnCurrentPage)
|
|
|
|
.map (field, idx) =>
|
2015-09-25 05:51:15 +08:00
|
|
|
errclass = if field.name in @state.errorFieldNames then "error " else ""
|
2015-09-23 11:11:51 +08:00
|
|
|
<label className={(field.className || "")} key={field.name}>
|
|
|
|
{field.label}
|
|
|
|
<input type={field.type}
|
|
|
|
tabIndex={idx + 1}
|
|
|
|
value={@state.fields[field.name]}
|
|
|
|
onChange={@_onValueChanged}
|
2015-10-01 02:58:40 +08:00
|
|
|
onKeyPress={@_onFieldKeyPress}
|
2015-09-23 11:11:51 +08:00
|
|
|
data-field={field.name}
|
2015-10-03 08:10:49 +08:00
|
|
|
data-format={field.format ? ""}
|
|
|
|
disabled={@state.tryingToAuthenticate}
|
2015-09-23 11:11:51 +08:00
|
|
|
className={errclass}
|
|
|
|
placeholder={field.placeholder} />
|
|
|
|
</label>
|
|
|
|
|
|
|
|
_renderSettings: =>
|
|
|
|
@state.provider.settings?.filter(@_fieldOnCurrentPage)
|
|
|
|
.map (field, idx) =>
|
|
|
|
if field.type is 'checkbox'
|
|
|
|
<label className={"checkbox #{field.className ? ""}"} key={field.name}>
|
|
|
|
<input type={field.type}
|
|
|
|
tabIndex={idx + 5}
|
|
|
|
checked={@state.settings[field.name]}
|
|
|
|
onChange={@_onSettingsChanged}
|
2015-10-01 02:58:40 +08:00
|
|
|
onKeyPress={@_onFieldKeyPress}
|
2015-09-23 11:11:51 +08:00
|
|
|
data-field={field.name}
|
2015-10-03 08:10:49 +08:00
|
|
|
disabled={@state.tryingToAuthenticate}
|
|
|
|
data-format={field.format ? ""}
|
2015-09-23 11:11:51 +08:00
|
|
|
className={field.className ? ""} />
|
|
|
|
{field.label}
|
|
|
|
</label>
|
|
|
|
else
|
2015-09-25 05:51:15 +08:00
|
|
|
errclass = if field.name in @state.errorFieldNames then "error " else ""
|
2015-09-23 11:11:51 +08:00
|
|
|
<label className={field.className ? ""}
|
|
|
|
style={if field.advanced and not @state.show_advanced then {display:'none'} else {}}
|
|
|
|
key={field.name}>
|
|
|
|
{field.label}
|
|
|
|
<input type={field.type}
|
|
|
|
tabIndex={idx + 5}
|
|
|
|
value={@state.settings[field.name]}
|
|
|
|
onChange={@_onSettingsChanged}
|
2015-10-01 02:58:40 +08:00
|
|
|
onKeyPress={@_onFieldKeyPress}
|
2015-09-23 11:11:51 +08:00
|
|
|
data-field={field.name}
|
2015-10-03 08:10:49 +08:00
|
|
|
data-format={field.format ? ""}
|
|
|
|
disabled={@state.tryingToAuthenticate}
|
2015-09-23 11:11:51 +08:00
|
|
|
className={errclass+(field.className ? "")}
|
|
|
|
placeholder={field.placeholder} />
|
|
|
|
</label>
|
|
|
|
|
|
|
|
_renderButton: =>
|
|
|
|
pages = @state.provider.pages || []
|
2015-09-26 08:43:36 +08:00
|
|
|
if pages.length > @state.pageNumber+1
|
|
|
|
<button className="btn btn-large btn-gradient" type="button" onClick={@_onNextButton}>Continue</button>
|
2015-09-23 11:11:51 +08:00
|
|
|
else if @state.provider.name isnt 'gmail'
|
2015-09-25 05:51:15 +08:00
|
|
|
if @state.tryingToAuthenticate
|
2015-10-03 08:10:49 +08:00
|
|
|
<button className="btn btn-large btn-disabled btn-add-account-spinning" type="button">
|
2015-09-26 08:43:36 +08:00
|
|
|
<RetinaImg name="sending-spinner.gif" width={15} height={15} mode={RetinaImg.Mode.ContentPreserve} /> Adding account…
|
2015-09-25 05:51:15 +08:00
|
|
|
</button>
|
|
|
|
else
|
2015-10-01 02:58:40 +08:00
|
|
|
<button className="btn btn-large btn-gradient btn-add-account" type="button" onClick={@_onSubmit}>Add account</button>
|
2015-09-23 11:11:51 +08:00
|
|
|
|
|
|
|
_onNextButton: (event) =>
|
2015-09-25 05:51:15 +08:00
|
|
|
@setState(pageNumber: @state.pageNumber + 1)
|
2015-09-23 11:11:51 +08:00
|
|
|
@_resize()
|
|
|
|
|
2015-09-25 05:51:15 +08:00
|
|
|
_onSubmit: (event) =>
|
2015-10-01 02:58:40 +08:00
|
|
|
return if @state.tryingToAuthenticate
|
|
|
|
|
2015-09-23 11:11:51 +08:00
|
|
|
data = settings: {}
|
|
|
|
for own k,v of @state.fields when v isnt ''
|
|
|
|
data[k] = v
|
|
|
|
for own k,v of @state.settings when v isnt ''
|
|
|
|
data.settings[k] = v
|
|
|
|
data.provider = @state.provider.name
|
|
|
|
|
2015-10-03 07:12:08 +08:00
|
|
|
# handle special case for exchange/outlook/hotmail username field
|
|
|
|
if data.provider in ['exchange','outlook','hotmail'] and not data.settings.username?.trim().length
|
2015-09-23 11:11:51 +08:00
|
|
|
data.settings.username = data.email
|
|
|
|
|
2015-09-25 05:51:15 +08:00
|
|
|
@setState(tryingToAuthenticate: true)
|
|
|
|
|
2015-09-23 11:11:51 +08:00
|
|
|
# Send the form data directly to Nylas to get code
|
2015-09-30 00:45:02 +08:00
|
|
|
# If this succeeds, send the received code to N1 server to register the account
|
2015-09-23 11:11:51 +08:00
|
|
|
# Otherwise process the error message from the server and highlight UI as needed
|
|
|
|
NylasAPI.makeRequest
|
|
|
|
path: "/auth?client_id=#{NylasAPI.AppID}"
|
|
|
|
method: 'POST'
|
|
|
|
body: data
|
|
|
|
returnsModel: false
|
2015-09-25 05:51:15 +08:00
|
|
|
timeout: 30000
|
2015-09-23 11:11:51 +08:00
|
|
|
auth:
|
|
|
|
user: ''
|
|
|
|
pass: ''
|
|
|
|
sendImmediately: true
|
|
|
|
.then (json) =>
|
|
|
|
EdgehillAPI.request
|
|
|
|
path: "/connect/nylas"
|
|
|
|
method: "POST"
|
2015-10-04 15:54:07 +08:00
|
|
|
timeout: 30000
|
2015-09-23 11:11:51 +08:00
|
|
|
body: json
|
|
|
|
success: (json) =>
|
2015-09-25 05:51:15 +08:00
|
|
|
OnboardingActions.accountJSONReceived(json)
|
|
|
|
error: @_onNetworkError
|
|
|
|
.catch(@_onNetworkError)
|
|
|
|
|
|
|
|
_onNetworkError: (err) =>
|
|
|
|
errorMessage = err.message
|
|
|
|
pageNumber = @state.pageNumber
|
|
|
|
errorFieldNames = err.body?.missing_fields || err.body?.missing_settings
|
|
|
|
|
|
|
|
if errorFieldNames
|
|
|
|
{pageNumber, errorMessage} = @_stateForMissingFieldNames(errorFieldNames)
|
|
|
|
if err.statusCode is -123 # timeout
|
|
|
|
errorMessage = "Request timed out. Please try again."
|
|
|
|
|
|
|
|
@setState
|
|
|
|
pageNumber: pageNumber
|
|
|
|
errorMessage: errorMessage
|
|
|
|
errorFieldNames: errorFieldNames || []
|
|
|
|
tryingToAuthenticate: false
|
|
|
|
@_resize()
|
|
|
|
|
|
|
|
_stateForMissingFieldNames: (fieldNames) ->
|
|
|
|
fieldLabels = []
|
|
|
|
fields = [].concat(@state.provider.settings, @state.provider.fields)
|
|
|
|
pageNumbers = [@state.pageNumber]
|
|
|
|
|
|
|
|
for fieldName in fieldNames
|
|
|
|
for s in fields when s.name is fieldName
|
|
|
|
fieldLabels.push(s.label.toLowerCase())
|
|
|
|
if s.page isnt undefined
|
|
|
|
pageNumbers.push(s.page)
|
|
|
|
|
|
|
|
pageNumber = Math.min.apply(null, pageNumbers)
|
|
|
|
errorMessage = @_messageForFieldLabels(fieldLabels)
|
|
|
|
|
|
|
|
{pageNumber, errorMessage}
|
2015-09-23 11:11:51 +08:00
|
|
|
|
2015-09-25 05:51:15 +08:00
|
|
|
_messageForFieldLabels: (labels) ->
|
|
|
|
if labels.length > 2
|
2015-09-23 11:11:51 +08:00
|
|
|
return "Please fix the highlighted fields."
|
2015-09-25 05:51:15 +08:00
|
|
|
else if labels.length is 2
|
|
|
|
return "Please provide your #{labels[0]} and #{labels[1]}."
|
2015-09-23 11:11:51 +08:00
|
|
|
else
|
2015-09-25 05:51:15 +08:00
|
|
|
return "Please provide your #{labels[0]}."
|
2015-09-23 11:11:51 +08:00
|
|
|
|
|
|
|
_pollForGmailAccount: (callback) =>
|
|
|
|
EdgehillAPI.request
|
|
|
|
path: "/oauth/google/token?key="+@state.provider.clientKey
|
|
|
|
method: "GET"
|
|
|
|
success: (json) =>
|
|
|
|
callback(json)
|
|
|
|
error: (err) =>
|
|
|
|
callback()
|
|
|
|
|
|
|
|
_resize: =>
|
|
|
|
setTimeout( =>
|
|
|
|
@props.onResize?()
|
|
|
|
,10)
|
|
|
|
|
|
|
|
_fireMoveToPrevPage: =>
|
|
|
|
if @state.pageNumber > 0
|
|
|
|
@setState(pageNumber: @state.pageNumber-1)
|
|
|
|
@_resize()
|
|
|
|
else
|
|
|
|
OnboardingActions.moveToPreviousPage()
|
|
|
|
|
|
|
|
module.exports = AccountSettingsPage
|