diff --git a/internal_packages/notifications/lib/headers/account-error-header.jsx b/internal_packages/notifications/lib/headers/account-error-header.jsx
index 1c713b945..f719a79fe 100644
--- a/internal_packages/notifications/lib/headers/account-error-header.jsx
+++ b/internal_packages/notifications/lib/headers/account-error-header.jsx
@@ -1,6 +1,7 @@
/* eslint global-require: 0 */
-import {AccountStore, Account, Actions, React} from 'nylas-exports'
+import {AccountStore, Account, Actions, React, IdentityStore} from 'nylas-exports'
import {RetinaImg} from 'nylas-component-kit'
+import {shell} from 'electron';
export default class AccountErrorHeader extends React.Component {
static displayName = 'AccountErrorHeader';
@@ -58,7 +59,15 @@ export default class AccountErrorHeader extends React.Component {
});
}
- renderErrorHeader(message, buttonName, actionCallback) {
+ _onUpgrade = () => {
+ this.setState({buildingUpgradeURL: true});
+ IdentityStore.fetchSingleSignOnURL('/dashboard').then((url) => {
+ this.setState({buildingUpgradeURL: false});
+ shell.openExternal(url);
+ });
+ }
+
+ _renderErrorHeader(message, buttonName, actionCallback) {
return (
+ )
+ }
+
render() {
const errorAccounts = this.state.accounts.filter(a => a.hasSyncStateError());
+ const trialExpiredAccounts = errorAccounts.filter(a => a.trialExpirationDate && (a.trialExpirationDate < new Date()))
+
+ if (trialExpiredAccounts.length > 0 || true) {
+ return this._renderUpgradeHeader(trialExpiredAccounts)
+ }
+
if (errorAccounts.length === 1) {
const account = errorAccounts[0];
switch (account.syncState) {
-
case Account.SYNC_STATE_AUTH_FAILED:
- return this.renderErrorHeader(
+ return this._renderErrorHeader(
`Nylas N1 can no longer authenticate with ${account.emailAddress}. Click here to reconnect.`,
"Reconnect",
() => this._reconnect(account));
case Account.SYNC_STATE_STOPPED:
- return this.renderErrorHeader(
+ return this._renderErrorHeader(
`The cloud sync for ${account.emailAddress} has been disabled. You will
not be able to send or receive mail. Please contact Nylas support.`,
"Contact support",
() => this._contactSupport());
default:
- return this.renderErrorHeader(
+ return this._renderErrorHeader(
`Nylas encountered an error while syncing mail for ${account.emailAddress} - we're
looking into it. Contact Nylas support for details.`,
"Contact support",
@@ -113,7 +153,7 @@ export default class AccountErrorHeader extends React.Component {
}
}
if (errorAccounts.length > 1) {
- return this.renderErrorHeader("Several of your accounts are having issues. " +
+ return this._renderErrorHeader("Several of your accounts are having issues. " +
"You will not be able to send or receive mail. Click here to manage your accounts.",
"Open preferences",
() => this._openPreferences());
diff --git a/internal_packages/notifications/stylesheets/notifications.less b/internal_packages/notifications/stylesheets/notifications.less
index 562b3806c..c9b6713b2 100644
--- a/internal_packages/notifications/stylesheets/notifications.less
+++ b/internal_packages/notifications/stylesheets/notifications.less
@@ -135,14 +135,15 @@
.notification-developer {
background-color: #615396;
}
+ .notification-upgrade {
+ background-image: -webkit-linear-gradient(bottom, #429E91, #40b1ac);
+ img { background-color: @text-color-inverse; }
+ }
.notification-error {
background: linear-gradient(to top, darken(@background-color-error, 4%) 0%, @background-color-error 100%);
border-color: @background-color-error;
color: @color-error;
}
- .notification-success {
- border-color: @background-color-success;
- }
.notification-offline {
background: linear-gradient(to top, darken(#CC9900, 4%) 0%, #CC9900 100%);
border-color: darken(#CC9900, 5%);
@@ -151,7 +152,7 @@
.notifications-sticky-item {
display:flex;
font-size: @font-size-base;
- color:@text-color-inverse;
+ color: @text-color-inverse;
border-bottom:1px solid rgba(0,0,0,0.25);
padding-left: @padding-base-horizontal;
line-height: @line-height-base * 1.5;
diff --git a/internal_packages/preferences/lib/tabs/preferences-identity.jsx b/internal_packages/preferences/lib/tabs/preferences-identity.jsx
index 48d679a74..e437dcb78 100644
--- a/internal_packages/preferences/lib/tabs/preferences-identity.jsx
+++ b/internal_packages/preferences/lib/tabs/preferences-identity.jsx
@@ -1,10 +1,11 @@
import React from 'react';
-import {Actions, NylasAPI, IdentityStore} from 'nylas-exports';
+import {Actions, IdentityStore} from 'nylas-exports';
import {RetinaImg} from 'nylas-component-kit';
+import {shell} from 'electron';
class OpenIdentityPageButton extends React.Component {
static propTypes = {
- destination: React.PropTypes.string,
+ path: React.PropTypes.string,
label: React.PropTypes.string,
img: React.PropTypes.string,
}
@@ -17,13 +18,10 @@ class OpenIdentityPageButton extends React.Component {
}
_onClick = () => {
- this.setState({loading: true});
- const identity = IdentityStore.identity();
- if (!identity) { return }
- NylasAPI.navigateToBillingSite()
- .then(() => {
- this.setState({loading: false})
- })
+ IdentityStore.fetchSingleSignOnURL(this.props.path).then((url) => {
+ this.setState({loading: false});
+ shell.openExternal(url);
+ });
}
render() {
@@ -37,7 +35,7 @@ class OpenIdentityPageButton extends React.Component {
}
if (this.props.img) {
return (
-
+
{this.props.label}
@@ -92,7 +90,7 @@ class PreferencesIdentity extends React.Component {
{firstname} {lastname}
{email}
-
+
Actions.logoutNylasIdentity()}>Sign Out
@@ -102,7 +100,7 @@ class PreferencesIdentity extends React.Component {
diff --git a/src/components/evented-iframe.cjsx b/src/components/evented-iframe.cjsx
index 3e85c6c08..69afdb311 100644
--- a/src/components/evented-iframe.cjsx
+++ b/src/components/evented-iframe.cjsx
@@ -1,7 +1,6 @@
React = require 'react'
ReactDOM = require 'react-dom'
{Utils,
- NylasAPI,
RegExpUtils,
IdentityStore,
SearchableComponentMaker,
@@ -163,7 +162,9 @@ class EventedIFrame extends React.Component
# If this is a link to our billing site, attempt single sign on instead of
# just following the link directly
if rawHref.startsWith(IdentityStore.URLRoot)
- NylasAPI.navigateToBillingSite(IdentityStore.identity(), '/billing')
+ path = rawHref.split(IdentityStore.URLRoot).pop()
+ IdentityStore.fetchSingleSignOnURL(IdentityStore.identity(), path).then (href) =>
+ NylasEnv.windowEventHandler.openLink(href: href, metaKey: e.metaKey)
return
# It's important to send the raw `href` here instead of the target.
diff --git a/src/flux/models/account.es6 b/src/flux/models/account.es6
index 0579d2e2a..89c569292 100644
--- a/src/flux/models/account.es6
+++ b/src/flux/models/account.es6
@@ -58,26 +58,27 @@ export default class Account extends ModelWithMetadata {
}),
label: Attributes.String({
- queryable: false,
modelKey: 'label',
}),
aliases: Attributes.Object({
- queryable: false,
modelKey: 'aliases',
}),
defaultAlias: Attributes.Object({
- queryable: false,
modelKey: 'defaultAlias',
jsonKey: 'default_alias',
}),
syncState: Attributes.String({
- queryable: false,
modelKey: 'syncState',
jsonKey: 'sync_state',
}),
+
+ trialExpirationDate: Attributes.DateTime({
+ modelKey: 'trialExpirationDate',
+ jsonKey: 'trial_expiration_date',
+ }),
});
constructor(args) {
diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee
index c62bec5ac..586e62659 100644
--- a/src/flux/nylas-api.coffee
+++ b/src/flux/nylas-api.coffee
@@ -115,6 +115,9 @@ class NylasAPI
if NylasEnv.getLoadSettings().isSpec
return Promise.resolve()
+ NylasAPIRequest ?= require('./nylas-api-request').default
+ req = new NylasAPIRequest(@, options)
+
success = (body) =>
if options.beforeProcessing
body = options.beforeProcessing(body)
@@ -124,19 +127,19 @@ class NylasAPI
Promise.resolve(body)
error = (err) =>
+ {url, auth, returnsModel} = req.options
+
handlePromise = Promise.resolve()
if err.response
- if err.response.statusCode is 404 and options.returnsModel
- handlePromise = @_handleModel404(options.url)
+ if err.response.statusCode is 404 and returnsModel
+ handlePromise = @_handleModel404(url)
if err.response.statusCode in [401, 403]
- handlePromise = @_handleAuthenticationFailure(options.url, options.auth?.user)
+ handlePromise = @_handleAuthenticationFailure(url, auth?.user)
if err.response.statusCode is 400
NylasEnv.reportError(err)
handlePromise.finally ->
Promise.reject(err)
- NylasAPIRequest ?= require('./nylas-api-request').default
- req = new NylasAPIRequest(@, options)
req.run().then(success, error)
longConnection: (opts) ->
diff --git a/src/flux/stores/identity-store.es6 b/src/flux/stores/identity-store.es6
index b04f06767..9ec86a3ab 100644
--- a/src/flux/stores/identity-store.es6
+++ b/src/flux/stores/identity-store.es6
@@ -2,6 +2,7 @@ import NylasStore from 'nylas-store';
import Actions from '../actions';
import keytar from 'keytar';
import {ipcRenderer} from 'electron';
+import request from 'request';
const configIdentityKey = "nylas.identity";
const keytarServiceName = 'Nylas';
@@ -24,6 +25,11 @@ class IdentityStore extends NylasStore {
this.trigger();
});
this._loadIdentity();
+
+ if (NylasEnv.isWorkWindow() && ['staging', 'production'].includes(NylasEnv.config.get('env'))) {
+ setInterval(this.refreshStatus, 1000 * 60 * 60);
+ this.refreshStatus();
+ }
}
_loadIdentity() {
@@ -42,7 +48,58 @@ class IdentityStore extends NylasStore {
}
trialDaysRemaining() {
- return this._trialDaysRemaining;
+ return 14;
+ }
+
+ refreshStatus = () => {
+ request({
+ method: 'GET',
+ url: `${this.URLRoot}/n1/user`,
+ auth: {
+ username: this._identity.token,
+ password: '',
+ sendImmediately: true,
+ },
+ }, (error, response = {}, body) => {
+ if (response.statusCode === 200) {
+ try {
+ const nextIdentity = Object.assign({}, this._identity, JSON.parse(body));
+ this._onSetNylasIdentity(nextIdentity)
+ } catch (err) {
+ NylasEnv.reportError("IdentityStore.refreshStatus: invalid JSON in response body.")
+ }
+ }
+ });
+ }
+
+ fetchSingleSignOnURL(path) {
+ if (!this._identity) {
+ return Promise.reject(new Error("fetchSingleSignOnURL: no identity set."));
+ }
+
+ if (!path.startsWith('/')) {
+ return Promise.reject(new Error("fetchSingleSignOnURL: path must start with a leading slash."));
+ }
+
+ return new Promise((resolve) => {
+ request({
+ method: 'POST',
+ url: `${this.URLRoot}/n1/login-link`,
+ json: true,
+ body: {
+ next_path: path,
+ account_token: this._identity.token,
+ },
+ }, (error, response = {}, body) => {
+ if (error || !body.startsWith('http')) {
+ // Single-sign on attempt failed. Rather than churn the user right here,
+ // at least try to open the page directly in the browser.
+ resolve(`${this.URLRoot}${path}`);
+ } else {
+ resolve(body);
+ }
+ });
+ });
}
_onLogoutNylasIdentity = () => {