diff --git a/internal_packages/account-error-header/assets/icon-alert-onred@1x.png b/internal_packages/account-error-header/assets/icon-alert-onred@1x.png
new file mode 100644
index 000000000..734d5d7fd
Binary files /dev/null and b/internal_packages/account-error-header/assets/icon-alert-onred@1x.png differ
diff --git a/internal_packages/account-error-header/assets/icon-alert-onred@2x.png b/internal_packages/account-error-header/assets/icon-alert-onred@2x.png
new file mode 100644
index 000000000..f76b1a1bd
Binary files /dev/null and b/internal_packages/account-error-header/assets/icon-alert-onred@2x.png differ
diff --git a/internal_packages/account-error-header/assets/icon-alert-sourcelist@1x.png b/internal_packages/account-error-header/assets/icon-alert-sourcelist@1x.png
new file mode 100644
index 000000000..a2e488fbc
Binary files /dev/null and b/internal_packages/account-error-header/assets/icon-alert-sourcelist@1x.png differ
diff --git a/internal_packages/account-error-header/assets/icon-alert-sourcelist@2x.png b/internal_packages/account-error-header/assets/icon-alert-sourcelist@2x.png
new file mode 100644
index 000000000..048627605
Binary files /dev/null and b/internal_packages/account-error-header/assets/icon-alert-sourcelist@2x.png differ
diff --git a/internal_packages/account-error-header/lib/account-error-header.jsx b/internal_packages/account-error-header/lib/account-error-header.jsx
new file mode 100644
index 000000000..f731a7b26
--- /dev/null
+++ b/internal_packages/account-error-header/lib/account-error-header.jsx
@@ -0,0 +1,93 @@
+import {AccountStore, Account, Actions, React} from 'nylas-exports'
+import {RetinaImg} from 'nylas-component-kit'
+
+export default class AccountErrorHeader extends React.Component {
+ static displayName = 'AccountErrorHeader';
+
+ constructor() {
+ super();
+ this.state = this.getStateFromStores();
+ }
+
+ componentDidMount() {
+ this.unsubscribe = AccountStore.listen(() => this._onAccountsChanged());
+ }
+
+ getStateFromStores() {
+ return {accounts: AccountStore.accounts()}
+ }
+
+ _onAccountsChanged() {
+ this.setState(this.getStateFromStores())
+ }
+
+ _reconnect(account) {
+ const ipc = require('electron').ipcRenderer;
+ ipc.send('command', 'application:add-account', account.provider);
+ }
+
+ _openPreferences() {
+ Actions.switchPreferencesTab('Accounts');
+ Actions.openPreferences()
+ }
+
+ _contactSupport() {
+ const {shell} = require("electron");
+ shell.openExternal("https://support.nylas.com/hc/en-us/requests/new");
+ }
+
+ renderErrorHeader(message, buttonName, actionCallback) {
+ return (
+
-
{label}
+
+ {label}
+
{accountSub} ({account.displayProvider()})
diff --git a/internal_packages/preferences/stylesheets/preferences-accounts.less b/internal_packages/preferences/stylesheets/preferences-accounts.less
index f845c945a..a3b67fc4d 100644
--- a/internal_packages/preferences/stylesheets/preferences-accounts.less
+++ b/internal_packages/preferences/stylesheets/preferences-accounts.less
@@ -18,11 +18,16 @@
border-bottom: 1px solid @border-color-divider;
}
+ .list-item:not(.selected) .sync-error {
+ color: @text-color-error;
+ }
+
.account-name {
font-size: @font-size-large;
cursor:default;
overflow: hidden;
text-overflow: ellipsis;
+ vertical-align: middle;
}
.account-subtext {
@@ -52,6 +57,29 @@
height: 140px;
}
+ .account-error-detail {
+ display: flex;
+ flex-direction: column;
+ background: linear-gradient(to top, #ca2541 0%, #d55268 100%);
+
+ .action {
+ flex-shrink: 0;
+ background-color: rgba(0,0,0,0.15);
+ text-align: center;
+ padding: 3px @padding-base-horizontal;
+ color: @text-color-inverse
+ }
+ .action:hover {
+ background-color: rgba(255,255,255,0.15);
+ text-decoration:none;
+ }
+ .message {
+ flex-grow: 1;
+ padding: 3px @padding-base-horizontal;
+ color: @text-color-inverse
+ }
+ }
+
.newsletter {
padding-top: @padding-base-vertical * 2;
input[type=checkbox] { margin: 0; position: relative; top: 0; }
diff --git a/internal_packages/worker-sync/lib/nylas-long-connection.coffee b/internal_packages/worker-sync/lib/nylas-long-connection.coffee
index 3ead7cb4d..e76333d33 100644
--- a/internal_packages/worker-sync/lib/nylas-long-connection.coffee
+++ b/internal_packages/worker-sync/lib/nylas-long-connection.coffee
@@ -98,7 +98,7 @@ class NylasLongConnection
@withCursor (cursor) =>
return if @state is NylasLongConnection.State.Ended
console.log("Delta Connection: Starting for account #{@_accountId}, token #{token}, with cursor #{cursor}")
- options = url.parse("#{@_api.APIRoot}/delta/streaming?cursor=#{cursor}&exclude_folders=false&exclude_metadata=false")
+ options = url.parse("#{@_api.APIRoot}/delta/streaming?cursor=#{cursor}&exclude_folders=false&exclude_metadata=false&exclude_account=false")
options.auth = "#{token}:"
if @_api.APIRoot.indexOf('https') is -1
diff --git a/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee b/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee
index e24499135..18cd7d973 100644
--- a/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee
+++ b/internal_packages/worker-sync/lib/nylas-sync-worker-pool.coffee
@@ -84,6 +84,14 @@ class NylasSyncWorkerPool
metadata = metadata.concat(_.values(deltas['metadata']))
delete deltas['metadata']
+ # Remove any account deltas, which are only used to notify broken/fixed sync state
+ # on accounts
+ delete create['account']
+ delete destroy['account']
+ if modify['account']
+ @_handleAccountDeltas(_.values(modify['account']))
+ delete modify['account']
+
# Apply all the deltas to create objects. Gets promises for handling
# each type of model in the `create` hash, waits for them all to resolve.
create[type] = NylasAPI._handleModelResponse(_.values(dict)) for type, dict of create
@@ -139,6 +147,10 @@ class NylasSyncWorkerPool
localMetadatum.version = metadatum.version
t.persistModel(model)
+ _handleAccountDeltas: (deltas) =>
+ for delta in deltas
+ Actions.updateAccount(delta.account_id, {syncState: delta.sync_state})
+
_handleDeltaDeletion: (delta) =>
klass = NylasAPI._apiObjectToClassMap[delta.object]
return unless klass
diff --git a/src/flux/action-bridge.coffee b/src/flux/action-bridge.coffee
index 893bead26..93a51132d 100644
--- a/src/flux/action-bridge.coffee
+++ b/src/flux/action-bridge.coffee
@@ -49,7 +49,7 @@ class ActionBridge
# Observe all global actions and re-broadcast them to other windows
Actions.globalActions.forEach (name) =>
- callback = => @onRebroadcast(TargetWindows.ALL, name, arguments)
+ callback = (args...) => @onRebroadcast(TargetWindows.ALL, name, args)
Actions[name].listen(callback, @)
# Observe the database store (possibly other stores in the future), and
@@ -63,7 +63,7 @@ class ActionBridge
# Observe all mainWindow actions fired in this window and re-broadcast
# them to other windows so the central application stores can take action
Actions.workWindowActions.forEach (name) =>
- callback = => @onRebroadcast(TargetWindows.WORK, name, arguments)
+ callback = (args...) => @onRebroadcast(TargetWindows.WORK, name, args)
Actions[name].listen(callback, @)
onIPCMessage: (event, initiatorId, name, json) =>
@@ -90,7 +90,7 @@ class ActionBridge
else
throw new Error("#{@initiatorId} received unknown action-bridge event: #{name}")
- onRebroadcast: (target, name, args...) =>
+ onRebroadcast: (target, name, args) =>
if Actions[name]?.firing
Actions[name].firing = false
return
@@ -99,7 +99,7 @@ class ActionBridge
args.forEach (arg) ->
if arg instanceof Function
throw new Error("ActionBridge cannot forward action argument of type `function` to work window.")
- params.push(arg[0])
+ params.push(arg)
json = JSON.stringify(params, Utils.registeredObjectReplacer)
diff --git a/src/flux/actions.coffee b/src/flux/actions.coffee
index fa667efb4..2f39b49a7 100644
--- a/src/flux/actions.coffee
+++ b/src/flux/actions.coffee
@@ -171,7 +171,7 @@ class Actions
Actions.updateAccount(account.id, {accountName: 'new'})
```
###
- @updateAccount: ActionScopeWindow
+ @updateAccount: ActionScopeGlobal
###
Public: Re-order the provided account in the account list.
diff --git a/src/flux/models/account.coffee b/src/flux/models/account.coffee
index 25c1a25a1..27664cf77 100644
--- a/src/flux/models/account.coffee
+++ b/src/flux/models/account.coffee
@@ -29,6 +29,11 @@ Section: Models
###
class Account extends ModelWithMetadata
+ @SYNC_STATE_RUNNING = "running"
+ @SYNC_STATE_STOPPED = "stopped"
+ @SYNC_STATE_AUTH_FAILED = "invalid"
+ @SYNC_STATE_ERROR = "sync_error"
+
@attributes: _.extend {}, ModelWithMetadata.attributes,
'name': Attributes.String
modelKey: 'name'
@@ -58,10 +63,16 @@ class Account extends ModelWithMetadata
modelKey: 'defaultAlias'
jsonKey: 'default_alias'
+ 'syncState': Attributes.String
+ queryable: false
+ modelKey: 'syncState'
+ jsonKey: 'sync_state'
+
constructor: ->
super
@aliases ||= []
@label ||= @emailAddress
+ @syncState ||= "running"
fromJSON: (json) ->
json["label"] ||= json[@constructor.attributes['emailAddress'].jsonKey]
diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee
index 0d4b85aab..46c966d92 100644
--- a/src/flux/nylas-api.coffee
+++ b/src/flux/nylas-api.coffee
@@ -127,12 +127,6 @@ class NylasAPI
NylasEnv.config.onDidChange('env', @_onConfigChanged)
@_onConfigChanged()
- if NylasEnv.isMainWindow()
- Actions.notificationActionTaken.listen ({notification, action}) ->
- if action.id is '401:reconnect'
- ipc = require('electron').ipcRenderer
- ipc.send('command', 'application:add-account', action.provider)
-
_onConfigChanged: =>
prev = {@AppID, @APIRoot, @APITokens}
@@ -245,17 +239,16 @@ class NylasAPI
type: 'error'
tag: '401'
sticky: true
- message: "Nylas N1 can no longer authenticate with #{email}. You
- will not be able to send or receive mail. Please click
- here to reconnect your account.",
+ message: "Action failed: There was an error syncing with #{email}. You
+ may not be able to send or receive mail.",
icon: 'fa-sign-out'
actions: [{
- default: true
- dismisses: true
- label: 'Reconnect'
- provider: account?.provider ? ""
- id: '401:reconnect'
- }]
+ default: true
+ dismisses: true
+ label: 'Dismiss'
+ provider: account?.provider ? ""
+ id: '401:dismiss'
+ }]
return Promise.resolve()
diff --git a/src/flux/stores/account-store.coffee b/src/flux/stores/account-store.coffee
index 78c2fcd74..448658320 100644
--- a/src/flux/stores/account-store.coffee
+++ b/src/flux/stores/account-store.coffee
@@ -61,6 +61,7 @@ class AccountStore extends NylasStore
# Inbound Events
_onUpdateAccount: (id, updated) =>
+ return unless NylasEnv.isMainWindow()
idx = _.findIndex @_accounts, (a) -> a.id is id
account = @_accounts[idx]
return if !account
diff --git a/static/images/notification/icon-alert-onred@1x.png b/static/images/notification/icon-alert-onred@1x.png
new file mode 100644
index 000000000..734d5d7fd
Binary files /dev/null and b/static/images/notification/icon-alert-onred@1x.png differ
diff --git a/static/images/notification/icon-alert-onred@2x.png b/static/images/notification/icon-alert-onred@2x.png
new file mode 100644
index 000000000..f76b1a1bd
Binary files /dev/null and b/static/images/notification/icon-alert-onred@2x.png differ
diff --git a/static/images/preferences/providers/ic-accountsettings-error@1x.png b/static/images/preferences/providers/ic-accountsettings-error@1x.png
new file mode 100644
index 000000000..f6733ccdb
Binary files /dev/null and b/static/images/preferences/providers/ic-accountsettings-error@1x.png differ
diff --git a/static/images/preferences/providers/ic-accountsettings-error@2x.png b/static/images/preferences/providers/ic-accountsettings-error@2x.png
new file mode 100644
index 000000000..916d82e4e
Binary files /dev/null and b/static/images/preferences/providers/ic-accountsettings-error@2x.png differ
diff --git a/static/images/preferences/providers/ic-settings-account-error@2x.png b/static/images/preferences/providers/ic-settings-account-error@2x.png
new file mode 100644
index 000000000..c396d2846
Binary files /dev/null and b/static/images/preferences/providers/ic-settings-account-error@2x.png differ