Coinbase: first step integration, connect account and main view

This commit is contained in:
Gustavo Maximiliano Cortez 2016-12-08 11:04:07 -03:00
commit af932b3e59
No known key found for this signature in database
GPG key ID: 15EDAD8D9F2EB1AF
8 changed files with 466 additions and 244 deletions

View file

@ -1,25 +1,53 @@
'use strict';
angular.module('copayApp.controllers').controller('coinbaseController',
function($rootScope, $scope, $timeout, $ionicModal, profileService, configService, storageService, coinbaseService, lodash, platformInfo, ongoingProcess) {
angular.module('copayApp.controllers').controller('coinbaseController', function($rootScope, $scope, $timeout, $ionicModal, $log, profileService, configService, storageService, coinbaseService, lodash, platformInfo, ongoingProcess, popupService, gettextCatalog, externalLinkService) {
var isNW = platformInfo.isNW;
var isNW = platformInfo.isNW;
if (platformInfo.isCordova && StatusBar.isVisible) {
StatusBar.backgroundColorByHexString("#4B6178");
}
var init = function() {
ongoingProcess.set('connectingCoinbase', true);
coinbaseService.init($scope.accessToken, function(err, data) {
console.log('[coinbase.js:9]',data); //TODO)
ongoingProcess.set('connectingCoinbase', false);
if (err || lodash.isEmpty(data)) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
return;
}
// Updating accessToken and accountId
$timeout(function() {
$scope.accessToken = data.accessToken;
$scope.accountId = data.accountId;
$scope.updateTransactions();
$scope.$apply();
}, 100);
});
};
this.openAuthenticateWindow = function() {
var oauthUrl = this.getAuthenticateUrl();
if (!isNW) {
$rootScope.openExternalLink(oauthUrl, '_system');
} else {
var self = this;
var gui = require('nw.gui');
var win = gui.Window.open(oauthUrl, {
focus: true,
position: 'center'
});
$scope.updateTransactions = function() {
$log.debug('Checking for transactions...');
coinbaseService.getPendingTransactions($scope.accessToken, $scope.accountId, function(err, txs) {
console.log('[coinbase.js:43]',txs); //TODO)
$scope.pendingTransactions = txs;
});
};
this.openAuthenticateWindow = function() {
var oauthUrl = this.getAuthenticateUrl();
externalLinkService.open(oauthUrl);
/*
* Not working (NW bug)
if (!isNW) {
externalLinkService.open(oauthUrl);
} else {
var self = this;
var gui = require('nw.gui');
gui.Window.open(oauthUrl, {
focus: true,
position: 'center'
}, function(win) {
win.on('loaded', function() {
var title = win.title;
if (title.indexOf('Coinbase') == -1) {
@ -28,51 +56,47 @@ angular.module('copayApp.controllers').controller('coinbaseController',
win.close();
}
});
}
}
this.getAuthenticateUrl = function() {
return coinbaseService.getOauthCodeUrl();
};
this.submitOauthCode = function(code) {
var self = this;
var coinbaseTestnet = configService.getSync().coinbase.testnet;
var network = coinbaseTestnet ? 'testnet' : 'livenet';
ongoingProcess.set('connectingCoinbase', true);
this.error = null;
$timeout(function() {
coinbaseService.getToken(code, function(err, data) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
self.error = err;
$timeout(function() {
$scope.$apply();
}, 100);
} else if (data && data.access_token && data.refresh_token) {
storageService.setCoinbaseToken(network, data.access_token, function() {
storageService.setCoinbaseRefreshToken(network, data.refresh_token, function() {
$scope.$emit('Local/CoinbaseUpdated', data.access_token);
$timeout(function() {
$scope.$apply();
}, 100);
});
});
}
});
}, 100);
};
this.openTxModal = function(tx) {
$scope.tx = tx;
$ionicModal.fromTemplateUrl('views/modals/coinbase-tx-details.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.coinbaseTxDetailsModal = modal;
$scope.coinbaseTxDetailsModal.show();
});
};
}
*/
}
this.getAuthenticateUrl = function() {
return coinbaseService.getOauthCodeUrl();
};
this.submitOauthCode = function(code) {
var self = this;
ongoingProcess.set('connectingCoinbase', true);
$scope.error = null;
$timeout(function() {
coinbaseService.getToken(code, function(err, accessToken) {
ongoingProcess.set('connectingCoinbase', false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
$scope.accessToken = accessToken;
init();
});
}, 100);
};
this.openTxModal = function(tx) {
$scope.tx = tx;
$ionicModal.fromTemplateUrl('views/modals/coinbase-tx-details.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.coinbaseTxDetailsModal = modal;
$scope.coinbaseTxDetailsModal.show();
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
coinbaseService.setCredentials();
$scope.network = coinbaseService.getEnvironment();
init();
});
});

View file

@ -926,21 +926,39 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
*
*/
.state('coinbase', {
.state('tabs.buyandsell.coinbase', {
url: '/coinbase',
templateUrl: 'views/coinbase.html'
views: {
'tab-home@tabs': {
controller: 'coinbaseController',
controllerAs: 'coinbase',
templateUrl: 'views/coinbase.html'
}
}
})
.state('preferencesCoinbase', {
url: '/preferencesCoinbase',
templateUrl: 'views/preferencesCoinbase.html'
.state('tabs.buyandsell.coinbase.preferences', {
url: '/preferences',
'tab-home@tabs': {
controller: 'preferencesCoinbaseController',
controllerAs: 'coinbase',
templateUrl: 'views/preferencesCoinbase.html'
}
})
.state('buyCoinbase', {
url: '/buycoinbase',
templateUrl: 'views/buyCoinbase.html'
.state('tabs.buyandsell.coinbase.buy', {
url: '/buy',
'tab-home@tabs': {
controller: 'buyCoinbaseController',
controllerAs: 'buy',
templateUrl: 'views/buyCoinbase.html'
}
})
.state('sellCoinbase', {
url: '/sellcoinbase',
templateUrl: 'views/sellCoinbase.html'
.state('tabs.buyandsell.coinbase.sell', {
url: '/sell',
'tab-home@tabs': {
controller: 'sellCoinbaseController',
controllerAs: 'sell',
templateUrl: 'views/sellCoinbase.html'
}
})
/*

View file

@ -1,11 +1,26 @@
'use strict';
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, platformInfo, lodash, storageService, configService) {
angular.module('copayApp.services').factory('coinbaseService', function($http, $log, $window, platformInfo, lodash, storageService, configService) {
var root = {};
var credentials = {};
var isCordova = platformInfo.isCordova;
var isNW = platformInfo.isNW;
root.setCredentials = function(network) {
root.setCredentials = function() {
if (!$window.externalServices || !$window.externalServices.coinbase) {
return;
}
var coinbase = $window.externalServices.coinbase;
/*
* Development: 'testnet'
* Production: 'livenet'
*/
credentials.NETWORK = 'livenet';
// Coinbase permissions
credentials.SCOPE = ''
+ 'wallet:accounts:read,'
+ 'wallet:addresses:read,'
@ -20,27 +35,45 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
+ 'wallet:transactions:send,'
+ 'wallet:payment-methods:read';
if (isCordova) {
credentials.REDIRECT_URI = 'copay://coinbase';
// NW has a bug with Window Object
if (isCordova && isNW) {
credentials.REDIRECT_URI = coinbase.redirect_uri.mobile;
} else {
credentials.REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
credentials.REDIRECT_URI = coinbase.redirect_uri.desktop;
}
if (network == 'testnet') {
credentials.HOST = 'https://sandbox.coinbase.com';
credentials.API = 'https://api.sandbox.coinbase.com';
credentials.CLIENT_ID = '6cdcc82d5d46654c46880e93ab3d2a43c639776347dd88022904bd78cd067841';
credentials.CLIENT_SECRET = '228cb6308951f4b6f41ba010c7d7981b2721a493c40c50fd2425132dcaccce59';
if (credentials.NETWORK == 'testnet') {
credentials.HOST = coinbase.sandbox.host;
credentials.API = coinbase.sandbox.api;
credentials.CLIENT_ID = coinbase.sandbox.client_id;
credentials.CLIENT_SECRET = coinbase.sandbox.client_secret;
}
else {
credentials.HOST = 'https://coinbase.com';
credentials.API = 'https://api.coinbase.com';
credentials.CLIENT_ID = window.coinbase_client_id;
credentials.CLIENT_SECRET = window.coinbase_client_secret;
credentials.HOST = coinbase.production.host;
credentials.API = coinbase.production.api;
credentials.CLIENT_ID = coinbase.production.client_id;
credentials.CLIENT_SECRET = coinbase.production.client_secret;
};
};
var _afterTokenReceived = function(data, cb) {
if (data && data.access_token && data.refresh_token) {
storageService.setCoinbaseToken(credentials.NETWORK, data.access_token, function() {
storageService.setCoinbaseRefreshToken(credentials.NETWORK, data.refresh_token, function() {
return cb(null, data.access_token);
});
});
} else {
return cb('Could not get the access token');
}
};
root.getEnvironment = function() {
return credentials.NETWORK;
};
root.getOauthCodeUrl = function() {
// TODO CHANGE LIMIT BACK TO 1000 *************************************************
return credentials.HOST
+ '/oauth/authorize?response_type=code&client_id='
+ credentials.CLIENT_ID
@ -48,13 +81,13 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
+ credentials.REDIRECT_URI
+ '&state=SECURE_RANDOM&scope='
+ credentials.SCOPE
+ '&meta[send_limit_amount]=1000&meta[send_limit_currency]=USD&meta[send_limit_period]=day';
+ '&meta[send_limit_amount]=1&meta[send_limit_currency]=USD&meta[send_limit_period]=day';
};
root.getToken = function(code, cb) {
var req = {
method: 'POST',
url: credentials.API + '/oauth/token',
url: credentials.HOST + '/oauth/token',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
@ -71,18 +104,18 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) {
$log.info('Coinbase Authorization Access Token: SUCCESS');
// Show pending task from the UI
storageService.setNextStep('BuyAndSell', true, function(err) {});
return cb(null, data.data);
storageService.setNextStep('BuyAndSell', 'true', function(err) {});
_afterTokenReceived(data.data, cb);
}, function(data) {
$log.error('Coinbase Authorization Access Token: ERROR ' + data.statusText);
return cb(data.data);
return cb(data.data || 'Could not get the access token');
});
};
root.refreshToken = function(refreshToken, cb) {
var _refreshToken = function(refreshToken, cb) {
var req = {
method: 'POST',
url: credentials.API + '/oauth/token',
url: credentials.HOST + '/oauth/token',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
@ -98,10 +131,63 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
$http(req).then(function(data) {
$log.info('Coinbase Refresh Access Token: SUCCESS');
return cb(null, data.data);
_afterTokenReceived(data.data, cb);
}, function(data) {
$log.error('Coinbase Refresh Access Token: ERROR ' + data.statusText);
return cb(data.data);
return cb(data.data || 'Could not get the access token');
});
};
var _getMainAccountId = function(accessToken, cb) {
root.getAccounts(accessToken, function(err, a) {
if (err) return cb(err);
var data = a.data;
for (var i = 0; i < data.length; i++) {
if (data[i].primary && data[i].type == 'wallet') {
return cb(null, data[i].id);
}
}
coinbaseService.logout(function() {});
return cb('Your primary account should be a WALLET. Set your wallet account as primary and try again');
});
};
root.init = function(accessToken, cb) {
if (lodash.isEmpty(credentials.CLIENT_ID)) {
return cb('Coinbase is Disabled');
}
$log.debug('Init Token...');
var getToken = function(cb) {
if (accessToken) {
cb(null, accessToken);
} else {
storageService.getCoinbaseToken(credentials.NETWORK, cb);
}
};
getToken(function(err, accessToken) {
if (err || !accessToken) return cb();
else {
_getMainAccountId(accessToken, function(err, accountId) {
if (err) {
if (err.errors && err.errors[0] && err.errors[0].id == 'expired_token') {
$log.debug('Refresh token');
storageService.getCoinbaseRefreshToken(credentials.NETWORK, function(err, refreshToken) {
if (err) return cb(err);
_refreshToken(refreshToken, function(err, newToken) {
if (err) return cb(err);
return cb(null, {accessToken: newToken, accountId: accountId});
});
});
} else {
return cb(err);
}
} else {
return cb(null, {accessToken: accessToken, accountId: accountId});
}
});
}
});
};
@ -124,7 +210,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
return cb(null, data.data);
}, function(data) {
$log.error('Coinbase Get Accounts: ERROR ' + data.statusText);
return cb(data.data);
return cb(data.data || 'Could not get the accounts');
});
};
@ -331,9 +417,8 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
// Pending transactions
root.savePendingTransaction = function(ctx, opts, cb) {
var network = configService.getSync().coinbase.testnet ? 'testnet' : 'livenet';
storageService.getCoinbaseTxs(network, function(err, oldTxs) {
var _savePendingTransaction = function(ctx, opts, cb) {
storageService.getCoinbaseTxs(credentials.NETWORK, function(err, oldTxs) {
if (lodash.isString(oldTxs)) {
oldTxs = JSON.parse(oldTxs);
}
@ -350,23 +435,166 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
}
tx = JSON.stringify(tx);
storageService.setCoinbaseTxs(network, tx, function(err) {
storageService.setCoinbaseTxs(credentials.NETWORK, tx, function(err) {
return cb(err);
});
});
};
root.getPendingTransactions = function(cb) {
var network = configService.getSync().coinbase.testnet ? 'testnet' : 'livenet';
storageService.getCoinbaseTxs(network, function(err, txs) {
var _txs = txs ? JSON.parse(txs) : {};
return cb(err, _txs);
root.getPendingTransactions = function(accessToken, accountId, cb) {
var coinbasePendingTransactions;
storageService.getCoinbaseTxs(credentials.NETWORK, function(err, txs) {
txs = txs ? JSON.parse(txs) : {};
coinbasePendingTransactions = lodash.isEmpty(txs) ? null : txs;
lodash.forEach(txs, function(dataFromStorage, txId) {
if ((dataFromStorage.type == 'sell' && dataFromStorage.status == 'completed') ||
(dataFromStorage.type == 'buy' && dataFromStorage.status == 'completed') ||
dataFromStorage.status == 'error' ||
(dataFromStorage.type == 'send' && dataFromStorage.status == 'completed')) return;
root.getTransaction(accessToken, accountId, txId, function(err, tx) {
if (err) {
_savePendingTransaction(dataFromStorage, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
});
return;
}
_updateCoinbasePendingTransactions(dataFromStorage, tx.data);
coinbasePendingTransactions[txId] = dataFromStorage;
if (tx.data.type == 'send' && tx.data.status == 'completed' && tx.data.from) {
root.sellPrice(accessToken, dataFromStorage.sell_price_currency, function(err, s) {
if (err) {
_savePendingTransaction(dataFromStorage, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
});
return cb();
}
var newSellPrice = s.data.amount;
var variance = Math.abs((newSellPrice - dataFromStorage.sell_price_amount) / dataFromStorage.sell_price_amount * 100);
if (variance < dataFromStorage.price_sensitivity.value) {
_sellPending(tx.data, accessToken, accountId);
} else {
var error = {
errors: [{
message: 'Price falls over the selected percentage'
}]
};
_savePendingTransaction(dataFromStorage, {
status: 'error',
error: error
}, function(err) {
if (err) $log.debug(err);
});
}
});
} else if (tx.data.type == 'buy' && tx.data.status == 'completed' && tx.data.buy) {
_sendToCopay(dataFromStorage, accessToken, accountId);
} else {
_savePendingTransaction(dataFromStorage, {}, function(err) {
if (err) $log.debug(err);
});
}
return cb(null, coinbasePendingTransactions);
});
});
});
};
root.logout = function(network, cb) {
storageService.removeCoinbaseToken(network, function() {
storageService.removeCoinbaseRefreshToken(network, function() {
var _sellPending = function(tx, accessToken, accountId) {
if (!tx) return;
var data = tx.amount;
data['commit'] = true;
root.sellRequest(accessToken, accountId, data, function(err, res) {
if (err) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
});
} else {
if (!res.data.transaction) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
});
return;
}
_savePendingTransaction(tx, {
remove: true
}, function(err) {
root.getTransaction(accessToken, accountId, res.data.transaction.id, function(err, updatedTx) {
_savePendingTransaction(updatedTx.data, {}, function(err) {
if (err) $log.debug(err);
});
});
});
}
});
};
var _sendToCopay = function(tx, accessToken, accountId) {
if (!tx) return;
var data = {
to: tx.toAddr,
amount: tx.amount.amount,
currency: tx.amount.currency,
description: 'To Copay Wallet'
};
root.sendTo(accessToken, accountId, data, function(err, res) {
if (err) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
});
} else {
if (!res.data.id) {
_savePendingTransaction(tx, {
status: 'error',
error: err
}, function(err) {
if (err) $log.debug(err);
});
return;
}
root.getTransaction(accessToken, accountId, res.data.id, function(err, sendTx) {
_savePendingTransaction(tx, {
remove: true
}, function(err) {
_savePendingTransaction(sendTx.data, {}, function(err) {
// TODO
});
});
});
}
});
};
var _updateCoinbasePendingTransactions = function(obj /*, …*/ ) {
for (var i = 1; i < arguments.length; i++) {
for (var prop in arguments[i]) {
var val = arguments[i][prop];
if (typeof val == "object")
_updateCoinbasePendingTransactions(obj[prop], val);
else
obj[prop] = val ? val : obj[prop];
}
}
return obj;
};
root.logout = function(cb) {
storageService.removeCoinbaseToken(credentials.NETWORK, function() {
storageService.removeCoinbaseRefreshToken(credentials.NETWORK, function() {
return cb();
});
});

View file

@ -49,7 +49,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer
},
coinbase: {
enabled: false, //disable coinbase for this release
enabled: true,
testnet: false
},
@ -230,10 +230,6 @@ angular.module('copayApp.services').factory('configService', function(storageSer
configCache.aliasFor = configCache.aliasFor || {};
configCache.emailFor = configCache.emailFor || {};
// Coinbase
// Disabled for testnet
configCache.coinbase.testnet = false;
$log.debug('Preferences read:', configCache)
lodash.each(root._queue, function(x) {

View file

@ -21,12 +21,9 @@
<span class="toggle-label" translate>Enable Glidera Service</span>
</ion-toggle>
<!-- disable coinbase for this release -->
<!-- <ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<ion-toggle ng-show="!isWP" ng-model="coinbaseEnabled.value" toggle-class="toggle-balanced" ng-change="coinbaseChange()">
<span class="toggle-label" translate>Enable Coinbase Service</span>
</ion-toggle> -->
</ion-toggle>
<div class="item item-divider" translate>Wallet Operation</div>

View file

@ -6,6 +6,10 @@
</ion-nav-bar>
<ion-content>
<ion-list>
<ion-item class="item item-icon-right" ui-sref="tabs.buyandsell.coinbase">
<img src="img/coinbase-logo.png" width="90">
<i class="icon bp-arrow-right"></i>
</ion-item>
<ion-item class="item item-icon-right" ui-sref="tabs.buyandsell.glidera">
<img src="img/glidera-logo.png" width="90">
<i class="icon bp-arrow-right"></i>

View file

@ -1,59 +1,30 @@
<div class="topbar-container">
<nav ng-controller="topbarController as topbar"
class="tab-bar"
ng-style="{'background-color': '#2b71b1'}">
<section class="left-small">
<a class="p10"
ng-click="topbar.goHome()">
<span class="text-close">Close</span>
</a>
</section>
<section class="right-small" ng-show="index.coinbaseAccount">
<a class="p10" href ui-sref="preferencesCoinbase">
<i class="fi-widget size-24"></i>
</a>
</section>
<section class="middle tab-bar-section">
<h1 class="title ellipsis">
Buy & Sell Bitcoin
</h1>
</section>
</nav>
</div>
<div class="content coinbase p20b" ng-controller="coinbaseController as coinbase">
<div class="row" ng-show="index.coinbaseError || (index.coinbaseToken && !index.coinbaseAccount)">
<div class="m20b box-notification" ng-show="index.coinbaseError">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in index.coinbaseError.errors" ng-bind-html="err.message"></li>
</ul>
</div>
<div class="m20b box-notification" ng-show="index.coinbaseToken && !index.coinbaseAccount">
<div class="text-warning">
<span>Your primary account should be a WALLET. Set your wallet account as primary and try again.</span>
</div>
</div>
<div class="m10t text-center">
<button
class="dark-gray outline round tiny"
ng-click="index.initCoinbase(index.coinbaseToken)">
Reconnect
<ion-view id="coinbase">
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>Coinbase</ion-nav-title>
<ion-nav-buttons side="secondary">
<button ng-show="accountId" class="button no-border" ui-sref="tabs.buyandsell.coinbase.preferences">
<i class="icon ion-ios-settings"></i>
</button>
<div class="m20t size-12">
Or go to <a class="text-gray" href ui-sref="preferencesCoinbase">Preferences</a> and log out manually.
</div>
</div>
</div>
</ion-nav-buttons>
</ion-nav-bar>
<div ng-if="!index.coinbaseToken && !index.coinbaseError" class="row">
<div class="box-notification text-center size-12 text-warning" ng-show="index.coinbaseTestnet">
<i class="fi-info"></i>
<ion-content>
<div class="box-notification warning m0" ng-show="network == 'testnet'">
Testnet wallets only work with Coinbase Sandbox Accounts
</div>
<div class="columns" ng-init="showOauthForm = false">
<div class="text-center" ng-show="error">
<div class="m20b box-notification" ng-show="error">
<ul class="no-bullet m0 size-12">
<li ng-repeat="err in error.errors" ng-bind-html="err.message"></li>
</ul>
</div>
</div>
<div ng-if="!accountId && !error" ng-init="showOauthForm = false">
<div class="text-center m20v">
<img src="img/coinbase-logo.png" width="200">
</div>
@ -61,111 +32,95 @@
<p class="m20t text-gray size-12">Connect your Coinbase account to get started</p>
<a class="button light-gray outline round small"
<button class="button button-standard button-primary"
ng-click="coinbase.openAuthenticateWindow(); showOauthForm = true">
Connect to Coinbase
</a>
<div>
</button>
<div class="m20t">
<a href ng-click="showOauthForm = true" class="text-gray size-12">
Do you already have the Oauth Code?
</a>
</div>
</div>
<div class="text-center" ng-show="showOauthForm">
<div ng-show="showOauthForm">
<div class="text-left box-notification" ng-show="coinbase.error">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in coinbase.error.errors" ng-bind-html="err.message"></li>
</ul>
</div>
<form name="oauthCodeForm" ng-submit="coinbase.submitOauthCode(code)" novalidate>
<label>OAuth Code</label>
<input type="text" ng-model="code" ng-disabled="coinbase.loading"
ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
<input
class="button expand round"
ng-style="{'background-color': '#2b71b1'}"
type="submit" value="Get started" ng-disabled="oauthCodeForm.$invalid || coinbase.loading">
</form>
<button class="button light-gray expand outline round"
ng-click="showOauthForm = false; index.coinbaseError = null; coinbase.error = null">
<i class="fi-arrow-left"></i> <span class="tu">Back</span>
</button>
</div>
</div>
</div>
<div ng-if="index.coinbaseToken && index.coinbaseAccount && !index.coinbaseError">
<div class="p20v text-center" ng-show="index.coinbaseAccount" ng-click="index.updateCoinbase({updateAccount: true})">
<img src="img/coinbase-logo.png" width="100">
</div>
<ul ng-show="index.coinbaseAccount" class="no-bullet m0 size-12">
<li class="line-b line-t p15 coinbase-pointer"
href ui-sref="buyCoinbase">
<img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="30">
<span class="m10 text-normal text-bold">Buy Bitcoin</span>
<span class="right text-gray">
<i class="icon-arrow-right3 size-24 right"></i>
</span>
</li>
<li class="line-b p15 coinbase-pointer"
href ui-sref="sellCoinbase">
<img src="img/sell-bitcoin.svg" alt="sell bitcoin" width="30">
<span class="m10 text-normal text-bold">Sell Bitcoin</span>
<span class="right text-gray">
<i class="icon-arrow-right3 size-24 right"></i>
</span>
</li>
</ul>
<div ng-show="index.coinbasePendingTransactions && !index.coinbaseError">
<h4 class="title">Activity</h4>
<div class="m20b box-notification" ng-show="index.coinbasePendingError">
<ul class="no-bullet m0 text-warning size-12">
<li ng-repeat="err in index.coinbasePendingError.errors" ng-bind-html="err.message"></li>
</ul>
</div>
<div ng-repeat="(id, tx) in index.coinbasePendingTransactions | orderObjectBy:'updated_at':true track by $index"
ng-click="coinbase.openTxModal(tx)"
class="row collapse coinbase-last-transactions-content">
<div class="large-2 medium-2 small-2 columns">
<img src="img/bought-pending.svg" alt="bought" width="24" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status != 'completed'">
<img src="img/bought.svg" alt="bought" width="30" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status == 'completed'">
<img src="img/sold-pending.svg" alt="sold" width="24" ng-show="tx.from && tx.type == 'send'">
<img src="img/sold.svg" alt="sold" width="30" ng-show="!tx.from && tx.type == 'sell' && tx.status == 'completed'">
</div>
<div class="large-5 medium-5 small-5 columns">
<div class="size-12 m5t">
<span ng-show="tx.type == 'sell' && tx.status == 'completed'">Sold</span>
<span ng-show="tx.type == 'buy' && tx.status == 'completed'">Bought</span>
<span class="text-bold">
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}}
</span>
<div class="list settings-input-group">
<label class="item item-input item-stacked-label">
<span class="input-label">OAuth Code</span>
<input type="text"
ng-model="code"
ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
</label>
</div>
</div>
<div class="large-4 medium-4 small-4 columns text-right">
<div ng-show="tx.error" class="m5t size-12 text-warning">
<input
class="button button-standard button-primary"
type="submit" value="Connect Coinbase Account" ng-disabled="oauthCodeForm.$invalid">
</form>
</div>
</div>
<div ng-if="accountId && !error">
<div class="list card"
ng-show="accountId">
<a class="item item-icon-right"
href ui-sref="tabs.buyandsell.coinbase.buy">
<img src="img/buy-bitcoin.svg" alt="buy bitcoin" width="35" class="item-img-buy">
Buy Bitcoin
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-right"
href ui-sref="tabs.buyandsell.coinbase.sell">
<img src="img/sell-bitcoin.svg" alt="buy bitcoin" width="35" class="item-img-sell">
Sell Bitcoin
<i class="icon bp-arrow-right"></i>
</a>
</div>
<div class="list card">
<div class="item item-heading" ng-click="updateTransactions()">
Activity
</div>
<a class="item"
ng-if="pendingTransactions && !error"
ng-repeat="(id, tx) in pendingTransactions | orderObjectBy:'updated_at':true track by $index"
ng-click="coinbase.openTxModal(tx)">
<span class="item-note">
<div ng-show="tx.error">
Error
</div>
<div ng-show="!tx.error" class="m5t size-12 text-gray">
<div ng-show="!tx.error">
<div ng-show="tx.status == 'completed'">
<time ng-if="tx.created_at">{{tx.created_at | amTimeAgo}}</time>
</div>
<div ng-show="tx.status == 'pending'">
<span class="label outline gray radius text-gray text-info" ng-if="tx.status == 'pending'">Pending</span>
<span ng-if="tx.status == 'pending'">Pending</span>
</div>
</div>
</div>
<div class="large-1 medium-1 small-1 columns text-right">
<i class="icon-arrow-right3 size-18"></i>
</div>
</div>
</span>
<img class="left m10r" src="img/bought-pending.svg" alt="bought" width="24" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status != 'completed'">
<img class="left m10r" src="img/bought.svg" alt="bought" width="30" ng-show="(tx.type == 'buy' || (tx.to && tx.type == 'send')) && tx.status == 'completed'">
<img class="left m10r" src="img/sold-pending.svg" alt="sold" width="24" ng-show="tx.from && tx.type == 'send'">
<img class="left m10r" src="img/sold.svg" alt="sold" width="30" ng-show="!tx.from && tx.type == 'sell' && tx.status == 'completed'">
<h2>
<span ng-show="tx.type == 'sell' && tx.status == 'completed'">Sold</span>
<span ng-show="tx.type == 'buy' && tx.status == 'completed'">Bought</span>
</h2>
<p>
<span ng-if="tx.type == 'sell' || (tx.type == 'send' && tx.from)">-</span>{{tx.amount.amount.replace('-','')}}
{{tx.amount.currency}}
</p>
</a>
</div>
</div>
<div class="extra-margin-bottom"></div>
</div>
</ion-content>
</ion-view>

View file

@ -124,10 +124,10 @@
<img src="img/glidera-logo.png" width="90"/>
<i class="icon bp-arrow-right"></i>
</a>
<!-- disable coinbase for this release -->
<!-- <a ng-if="coinbaseEnabled" ui-sref="exchange.coinbase" class="item">
<img src="img/coinbase-logo.png" width="90"> TODO
</a> -->
<a ng-if="coinbaseEnabled" ui-sref="tabs.buyandsell.coinbase" class="item item-extra-padding item-sub item-icon-right">
<img src="img/coinbase-logo.png" width="90">
<i class="icon bp-arrow-right"></i>
</a>
</div>
</div>
@ -156,7 +156,7 @@
<span translate>Add BitPay Visa&reg; Card</span>
<i class="icon bp-arrow-right"></i>
</a>
<a ng-if="!externalServices.BuyAndSell && (coinbaseEnabled || glideraEnabled)" ui-sref="tabs.buyandsell.glidera" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<a ng-if="!externalServices.BuyAndSell && (coinbaseEnabled || glideraEnabled)" ui-sref="tabs.buyandsell" class="item item-sub item-icon-left item-big-icon-left item-icon-right next-step">
<i class="icon big-icon-svg">
<div class="bg icon-buy-bitcoin"></div>
</i>