diff --git a/src/js/controllers/bitpayCardIntro.js b/src/js/controllers/bitpayCardIntro.js index 9fea27978..8abe4dfeb 100644 --- a/src/js/controllers/bitpayCardIntro.js +++ b/src/js/controllers/bitpayCardIntro.js @@ -1,5 +1,5 @@ 'use strict'; -angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, appIdentityService, bitpayService, lodash) { +angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService, bitpayAccountService) { $scope.$on("$ionicView.beforeEnter", function(event, data) { if (data.stateParams && data.stateParams.secret) { @@ -8,11 +8,8 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f email: data.stateParams.email, otp: data.stateParams.otp }; - - var pairingReason = gettextCatalog.getString('BitPay Visa card'); - - bitpayService.pair(pairData, pairingReason, function(err, paired, apiContext) { - + var pairingReason = gettextCatalog.getString('add your BitPay Visa card(s)'); + bitpayAccountService.pair(pairData, pairingReason, function(err, paired, apiContext) { if (err) { popupService.showAlert(gettextCatalog.getString('Error pairing Bitpay Account'), err); return; @@ -25,26 +22,28 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f } // Set flag for nextStep storageService.setNextStep('BitpayCard', 'true', function(err) {}); - $ionicHistory.nextViewOptions({ disableAnimate: true }); $state.go('tabs.home').then(function() { - if (cards[0]) { + if (data.cards[0]) { $state.transitionTo('tabs.bitpayCard', { - id: cards[0].id + id: data.cards[0].id }); } }); }); } }); - } else { - appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { - if (err) popupService.showAlert(null, err); - else $log.info('App identity: OK'); - }); } + + bitpayAccountService.getAccounts(function(err, accounts) { + if (err) { + popupService.showAlert(gettextCatalog.getString('Error'), err); + return; + } + $scope.accounts = accounts; + }); }); $scope.bitPayCardInfo = function() { @@ -58,7 +57,37 @@ angular.module('copayApp.controllers').controller('bitpayCardIntroController', f }; $scope.connectBitPayCard = function() { - var url = 'https://bitpay.com/visa/dashboard/add-to-bitpay-wallet-confirm'; - externalLinkService.open(url); + if ($scope.accounts.length == 0) { + startPairBitPayAccount(); + } else { + showAccountSelector(); + } }; + + var startPairBitPayAccount = function() { + var url = 'https://bitpay.com/visa/dashboard/add-to-bitpay-wallet-confirm'; + externalLinkService.open(url); + }; + + var showAccountSelector = function() { + $scope.accountSelectorTitle = gettextCatalog.getString('From BitPay account'); + $scope.showAccounts = ($scope.accounts != undefined); + }; + + $scope.onAccountSelect = function(account) { + if (account == undefined) { + startPairBitPayAccount(); + } else { + bitpayCardService.fetchBitpayDebitCards(account.apiContext, function(err, data) { + if (err) { + popupService.showAlert(gettextCatalog.getString('Error'), err); + return; + } + storageService.setNextStep('BitpayCard', 'true', function(err) { + $state.go('tabs.home'); + }); + }); + } + }; + }); diff --git a/src/js/controllers/preferencesBitpayServices.js b/src/js/controllers/preferencesBitpayServices.js new file mode 100644 index 000000000..c6e0343f5 --- /dev/null +++ b/src/js/controllers/preferencesBitpayServices.js @@ -0,0 +1,75 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('preferencesBitpayServicesController', + function($rootScope, $scope, $state, $timeout, $ionicHistory, bitpayAccountService, bitpayCardService, popupService, gettextCatalog) { + + $scope.removeAccount = function(account) { + var title = gettextCatalog.getString('Remove BitPay Account?'); + var msg = gettextCatalog.getString('Removing your BitPay account will remove all associated BitPay account data from this device.

Are you sure you would like to remove your BitPay Account ({{email}}) from this device?', { + email: account.email + }); + popupService.showConfirm(title, msg, null, null, function(res) { + if (res) { + removeAccount(account); + } + }); + }; + + $scope.removeCard = function(card) { + var title = gettextCatalog.getString('Remove BitPay Card?'); + var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card ({{lastFourDigits}}) from this device?', { + lastFourDigits: card.lastFourDigits + }); + popupService.showConfirm(title, msg, null, null, function(res) { + if (res) { + removeCard(card); + } + }); + }; + + var removeAccount = function(account) { + bitpayAccountService.removeAccount(account, function(err) { + if (err) { + return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove account')); + } + setScope(function() { + // If there are no paired accounts then change views. + if ($scope.bitpayAccounts.length == 0) { + $state.go('tabs.settings').then(function() { + $ionicHistory.clearHistory(); + $state.go('tabs.home'); + }); + } + }); + }); + }; + + var removeCard = function(card) { + bitpayCardService.removeCard(card, function(err) { + if (err) { + return popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not remove card')); + } + setScope(); + }); + }; + + var setScope = function(cb) { + bitpayAccountService.getAccounts(function(err, data) { + if (err) return; + $scope.bitpayAccounts = data; + + bitpayCardService.getBitpayDebitCards(function(err, data) { + if (err) return; + $scope.bitpayCards = data; + if (cb) { + cb(); + } + }); + }); + }; + + $scope.$on("$ionicView.beforeEnter", function(event, data) { + setScope(); + }); + + }); diff --git a/src/js/controllers/tab-settings.js b/src/js/controllers/tab-settings.js index 98d2b1bf5..ab42a38de 100644 --- a/src/js/controllers/tab-settings.js +++ b/src/js/controllers/tab-settings.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayCardService, storageService, glideraService, coinbaseService, gettextCatalog, buyAndSellService) { +angular.module('copayApp.controllers').controller('tabSettingsController', function($scope, appConfigService, $ionicModal, $log, lodash, uxLanguage, platformInfo, profileService, feeService, configService, externalLinkService, bitpayAccountService, bitpayCardService, storageService, glideraService, gettextCatalog) { var updateConfig = function() { var isCordova = platformInfo.isCordova; @@ -25,12 +25,27 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct isoCode: config.wallet.settings.alternativeIsoCode }; - // TODO move this to a generic service - bitpayCardService.getCards(function(err, cards) { + $scope.bitpayCardEnabled = config.bitpayCard.enabled; + $scope.glideraEnabled = config.glidera.enabled && !isWindowsPhoneApp; + + bitpayAccountService.getAccounts(function(err, data) { if (err) $log.error(err); - $scope.bitpayCards = cards && cards.length > 0; + $scope.bitpayAccounts = !lodash.isEmpty(data); }); + if ($scope.bitpayCardEnabled) { + bitpayCardService.getBitpayDebitCards(function(err, cards) { + if (err) $log.error(err); + $scope.bitpayCards = cards && cards.length > 0; + }); + } + + if ($scope.glideraEnabled) { + storageService.getGlideraToken(glideraService.getEnvironment(), function(err, token) { + if (err) $log.error(err); + $scope.glideraToken = token; + }); + } }); }; diff --git a/src/js/directives/accountSelector.js b/src/js/directives/accountSelector.js new file mode 100644 index 000000000..4e7937b4a --- /dev/null +++ b/src/js/directives/accountSelector.js @@ -0,0 +1,28 @@ +'use strict'; + +angular.module('copayApp.directives') + .directive('accountSelector', function($timeout) { + return { + restrict: 'E', + templateUrl: 'views/includes/accountSelector.html', + transclude: true, + scope: { + title: '=accountSelectorTitle', + show: '=accountSelectorShow', + accounts: '=accountSelectorAccounts', + selectedAccount: '=accountSelectorSelectedAccount', + onSelect: '=accountSelectorOnSelect' + }, + link: function(scope, element, attrs) { + scope.hide = function() { + scope.show = false; + }; + scope.selectAccount = function(account) { + $timeout(function() { + scope.hide(); + }, 100); + scope.onSelect(account); + }; + } + }; + }); diff --git a/src/js/routes.js b/src/js/routes.js index 59202bea4..7fa8257f0 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -1078,12 +1078,12 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr paypro: null } }) - .state('tabs.preferences.bitpayCard', { - url: '/bitpay-card', + .state('tabs.preferences.bitpayServices', { + url: '/bitpay-services', views: { 'tab-settings@tabs': { - controller: 'preferencesBitpayCardController', - templateUrl: 'views/preferencesBitpayCard.html' + controller: 'preferencesBitpayServicesController', + templateUrl: 'views/preferencesBitpayServices.html' } } }); diff --git a/src/js/services/bitpayAccountService.js b/src/js/services/bitpayAccountService.js new file mode 100644 index 000000000..232bd17d8 --- /dev/null +++ b/src/js/services/bitpayAccountService.js @@ -0,0 +1,206 @@ +'use strict'; + +angular.module('copayApp.services').factory('bitpayAccountService', function($log, lodash, platformInfo, appIdentityService, bitpayService, bitpayCardService, storageService, gettextCatalog, popupService) { + var root = {}; + + /* + * Pair this app with the bitpay server using the specified pairing data. + * An app identity will be created if one does not already exist. + * Pairing data is provided by an input URI provided by the bitpay server. + * + * pairData - data needed to complete the pairing process + * { + * secret: shared pairing secret + * email: email address associated with bitpay account + * otp: two-factor one-time use password + * } + * + * pairingReason - text string to be embedded into popup message. If `null` then the reason + * message is not shown to the UI. + * "To {{reason}} you must pair this app with your BitPay account ({{email}})." + * + * cb - callback after completion + * callback(err, paired, apiContext) + * + * err - something unexpected happened which prevented the pairing + * + * paired - boolean indicating whether the pairing was compledted by the user + * + * apiContext - the context needed for making future api calls + * { + * token: api token for use in future calls + * pairData: the input pair data + * appIdentity: the identity of this app + * } + */ + root.pair = function(pairData, pairingReason, cb) { + checkOtp(pairData, function(otp) { + pairData.otp = otp; + var deviceName = 'Unknown device'; + if (platformInfo.isNW) { + deviceName = require('os').platform(); + } else if (platformInfo.isCordova) { + deviceName = device.model; + } + var json = { + method: 'createToken', + params: { + secret: pairData.secret, + version: 2, + deviceName: deviceName, + code: pairData.otp + } + }; + + bitpayService.postAuth(json, function(data) { + if (data && data.data.error) { + return cb(data.data.error); + } + var apiContext = { + token: data.data.data, + pairData: pairData, + appIdentity: data.appIdentity + }; + $log.info('BitPay service BitAuth create token: SUCCESS'); + + fetchBasicInfo(apiContext, function(err, basicInfo) { + if (err) return cb(err); + var title = gettextCatalog.getString('Add BitPay Account?'); + var msgDetail = 'Add this BitPay account ({{email}})?'; + if (pairingReason) { + msgDetail = 'To {{reason}} you must first add your BitPay account.

{{email}}'; + } + var msg = gettextCatalog.getString(msgDetail, { + reason: pairingReason, + email: pairData.email + }); + var ok = gettextCatalog.getString('Add Account'); + var cancel = gettextCatalog.getString('Go back'); + popupService.showConfirm(title, msg, ok, cancel, function(res) { + if (res) { + var acctData = { + token: apiContext.token, + email: pairData.email, + givenName: basicInfo.givenName, + familyName: basicInfo.familyName + }; + setBitpayAccount(acctData, function(err) { + return cb(err, true, apiContext); + }); + } else { + $log.info('User cancelled BitPay pairing process'); + return cb(null, false); + } + }); + }); + }, function(data) { + return cb(_setError('BitPay service BitAuth create token: ERROR ', data)); + }); + }); + }; + + var checkOtp = function(pairData, cb) { + if (pairData.otp) { + var msg = gettextCatalog.getString('Enter Two Factor for your BitPay account'); + popupService.showPrompt(null, msg, null, function(res) { + cb(res); + }); + } else { + cb(); + } + }; + + var fetchBasicInfo = function(apiContext, cb) { + var json = { + method: 'getBasicInfo' + }; + // Get basic account information + bitpayService.post('/api/v2/' + apiContext.token, json, function(data) { + if (data && data.data.error) return cb(data.data.error); + $log.info('BitPay Account Get Basic Info: SUCCESS'); + return cb(null, data.data.data); + }, function(data) { + return cb(_setError('BitPay Account Error: Get Basic Info', data)); + }); + }; + + // Returns account objects as stored. + root.getAccountsAsStored = function(cb) { + storageService.getBitpayAccounts(bitpayService.getEnvironment().network, cb); + }; + + // Returns an array where each element represents an account including all information required for fetching data + // from the server for each account (apiContext). + root.getAccounts = function(cb) { + root.getAccountsAsStored(function(err, accounts) { + if (err || !accounts) { + return cb(err, []); + } + appIdentityService.getIdentity(bitpayService.getEnvironment().network, function(err, appIdentity) { + if (err) { + return cb(err); + } + + var accountsArray = []; + lodash.forEach(Object.keys(accounts), function(key) { + accounts[key].bitpayDebitCards = accounts[key]['bitpayDebitCards-' + bitpayService.getEnvironment().network]; + accounts[key].email = key; + accounts[key].firstName = accounts[key]['basicInfo-' + bitpayService.getEnvironment().network].givenName; + accounts[key].lastName = accounts[key]['basicInfo-' + bitpayService.getEnvironment().network].familyName; + accounts[key].apiContext = { + token: accounts[key]['bitpayApi-' + bitpayService.getEnvironment().network].token, + pairData: { + email: key + }, + appIdentity: appIdentity + }; + + // Remove environment keyed attributes. + delete accounts[key]['bitpayApi-' + bitpayService.getEnvironment().network]; + delete accounts[key]['bitpayDebitCards-' + bitpayService.getEnvironment().network]; + + accountsArray.push(accounts[key]); + }); + return cb(null, accountsArray); + }); + }); + }; + + var setBitpayAccount = function(account, cb) { + var data = JSON.stringify(account); + storageService.setBitpayAccount(bitpayService.getEnvironment().network, data, function(err) { + if (err) { + return cb(err); + } + return cb(); + }); + }; + + root.removeAccount = function(account, cb) { + storageService.removeBitpayAccount(bitpayService.getEnvironment().network, account, function(err) { + if (err) { + $log.error('Error removing BitPay account: ' + err); + // Continue, try to remove next step if necessary + } + storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, cards) { + if (err) { + $log.error('Error attempting to get BitPay debit cards after account removal: ' + err); + } + if (cards.length == 0) { + storageService.removeNextStep('BitpayCard', cb); + } else { + cb(); + } + }); + }); + }; + + var _setError = function(msg, e) { + $log.error(msg); + var error = (e && e.data && e.data.error) ? e.data.error : msg; + return error; + }; + + return root; + +}); diff --git a/src/js/services/bitpayCardService.js b/src/js/services/bitpayCardService.js index 4f87bba50..f8b8b207c 100644 --- a/src/js/services/bitpayCardService.js +++ b/src/js/services/bitpayCardService.js @@ -47,28 +47,13 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, bitpayService.post('/api/v2/' + apiContext.token, json, function(data) { if (data && data.data.error) return cb(data.data.error); $log.info('BitPay Get Debit Cards: SUCCESS'); - - var cards = []; - - lodash.each(data.data.data, function(x) { - var n = {}; - - if (!x.eid || !x.id || !x.lastFourDigits || !x.token) { - $log.warn('BAD data from Bitpay card' + JSON.stringify(x)); - return; - } - - n.eid = x.eid; - n.id = x.id; - n.lastFourDigits = x.lastFourDigits; - n.token = x.token; - cards.push(n); - }); - - storageService.setBitpayDebitCards(bitpayService.getEnvironment().network, apiContext.pairData.email, cards, function(err) { - register(); - - return cb(err, cards); + // Cache card data in storage + var cardData = { + cards: data.data.data, + email: apiContext.pairData.email + } + root.setBitpayDebitCards(cardData, function(err) { + return cb(err, {token: apiContext.token, cards: data.data.data, email: apiContext.pairData.email}); }); }, function(data) { return cb(_setError('BitPay Card Error: Get Debit Cards', data)); @@ -201,15 +186,32 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log, }, cb); }; - - root.remove = function(cardId, cb) { - storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, cardId, function(err) { + root.removeCard = function(card, cb) { + storageService.removeBitpayDebitCard(bitpayService.getEnvironment().network, card, function(err) { if (err) { $log.error('Error removing BitPay debit card: ' + err); - return cb(err); + // Continue, try to remove/cleanup next step and card history } - register(); - storageService.removeBalanceCache(cardId, cb); + // Next two items in parallel + // + // If there are no more cards in storage then re-enable the next step entry + storageService.getBitpayDebitCards(bitpayService.getEnvironment().network, function(err, cards) { + if (err) { + $log.error('Error getting BitPay debit cards after remove: ' + err); + // Continue, try to remove next step if necessary + } + if (cards.length == 0) { + storageService.removeNextStep('BitpayCard', cb); + } + }); + storageService.removeBitpayDebitCardHistory(bitpayService.getEnvironment().network, card, function(err) { + if (err) { + $log.error('Error removing BitPay debit card transaction history: ' + err); + return cb(err); + } + $log.info('Successfully removed BitPay debit card'); + return cb(); + }); }); }; diff --git a/src/js/services/bitpayService.js b/src/js/services/bitpayService.js index 743d58922..28035c9b6 100644 --- a/src/js/services/bitpayService.js +++ b/src/js/services/bitpayService.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.services').factory('bitpayService', function($log, $http, platformInfo, appIdentityService, bitauthService, storageService, gettextCatalog, popupService, ongoingProcess) { +angular.module('copayApp.services').factory('bitpayService', function($log, $http, appIdentityService, bitauthService) { var root = {}; var NETWORK = 'livenet'; @@ -12,99 +12,6 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt }; }; - /* - * Pair this app with the bitpay server using the specified pairing data. - * An app identity will be created if one does not already exist. - * Pairing data is provided by an input URI provided by the bitpay server. - * - * pairData - data needed to complete the pairing process - * { - * secret: shared pairing secret - * email: email address associated with bitpay account - * otp: two-factor one-time use password - * } - * - * pairingReason - text string to be embedded into popup message. If `null` then the reason - * message is not shown to the UI. - * "To {{reason}} you must pair this app with your BitPay account ({{email}})." - * - * cb - callback after completion - * callback(err, paired, apiContext) - * - * err - something unexpected happened which prevented the pairing - * - * paired - boolean indicating whether the pairing was compledted by the user - * - * apiContext - the context needed for making future api calls - * { - * token: api token for use in future calls - * pairData: the input pair data - * appIdentity: the identity of this app - * } - */ - root.pair = function(pairData, pairingReason, cb) { - checkOtp(pairData, function(otp) { - pairData.otp = otp; - var deviceName = 'Unknown device'; - if (platformInfo.isNW) { - deviceName = require('os').platform(); - } else if (platformInfo.isCordova) { - deviceName = device.model; - } - var json = { - method: 'createToken', - params: { - secret: pairData.secret, - version: 2, - deviceName: deviceName, - code: pairData.otp - } - }; - appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) { - if (err) return cb(err); - ongoingProcess.set('fetchingBitPayAccount', true); - $http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) { - ongoingProcess.set('fetchingBitPayAccount', false); - - if (data && data.data.error) return cb(data.data.error); - $log.info('BitPay service BitAuth create token: SUCCESS'); - var title = gettextCatalog.getString('Link BitPay Account?'); - var msgDetail = 'Link BitPay account ({{email}})?'; - if (pairingReason) { - msgDetail = 'To add your {{reason}} please link your BitPay account {{email}}'; - } - var msg = gettextCatalog.getString(msgDetail, { - reason: pairingReason, - email: pairData.email - }); - var ok = gettextCatalog.getString('Confirm'); - var cancel = gettextCatalog.getString('Cancel'); - popupService.showConfirm(title, msg, ok, cancel, function(res) { - if (res) { - var acctData = { - token: data.data.data, - email: pairData.email - }; - setBitpayAccount(acctData, function(err) { - if (err) return cb(err); - return cb(null, true, { - token: acctData.token, - pairData: pairData, - appIdentity: appIdentity - }); - }); - } else { - $log.info('User cancelled BitPay pairing process'); - return cb(null, false); - } - }); - }, function(data) { - return cb(_setError('BitPay service BitAuth create token: ERROR ', data)); - }); - }); - }); - }; - root.get = function(endpoint, successCallback, errorCallback) { $http(_get(endpoint)).then(function(data) { successCallback(data); @@ -115,7 +22,9 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt root.post = function(endpoint, json, successCallback, errorCallback) { appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) { - if (err) return errorCallback(err); + if (err) { + return errorCallback(err); + } $http(_post(endpoint, json, appIdentity)).then(function(data) { successCallback(data); }, function(data) { @@ -124,22 +33,20 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt }); }; - var checkOtp = function(pairData, cb) { - if (pairData.otp) { - var msg = gettextCatalog.getString('Enter Two Factor for your BitPay account'); - popupService.showPrompt(null, msg, null, function(res) { - cb(res); + root.postAuth = function(json, successCallback, errorCallback) { + appIdentityService.getIdentity(root.getEnvironment().network, function(err, appIdentity) { + if (err) { + return errorCallback(err); + } + $http(_postAuth('/api/v2/', json, appIdentity)).then(function(data) { + data.appIdentity = appIdentity; + successCallback(data); + }, function(data) { + errorCallback(data); }); - } else { - cb(); - } + }); }; - var setBitpayAccount = function(accountData, cb) { - storageService.setBitpayAccount(root.getEnvironment().network, accountData, cb); - }; - - var _get = function(endpoint) { return { method: 'GET', @@ -184,12 +91,6 @@ angular.module('copayApp.services').factory('bitpayService', function($log, $htt return ret; }; - var _setError = function(msg, e) { - $log.error(msg); - var error = (e && e.data && e.data.error) ? e.data.error : msg; - return error; - }; - return root; }); diff --git a/src/js/services/configService.js b/src/js/services/configService.js index 3b2ef3826..1797eab9f 100644 --- a/src/js/services/configService.js +++ b/src/js/services/configService.js @@ -190,6 +190,9 @@ angular.module('copayApp.services').factory('configService', function(storageSer if (!configCache.pushNotifications) { configCache.pushNotifications = defaultConfig.pushNotifications; } + if (!configCache.bitpayAccount) { + configCache.bitpayAccount = defaultConfig.bitpayAccount; + } } else { configCache = lodash.clone(defaultConfig); diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index 5f78725a3..315a28386 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -362,17 +362,25 @@ angular.module('copayApp.services') // lastFourDigits: card number // token: card token // ] - root.setBitpayDebitCards = function(network, email, cards, cb) { - - root.getBitpayAccounts(network, function(err, allAccounts) { + // email: account email + // token: account token + // } + root.setBitpayDebitCards = function(network, data, cb) { + if (lodash.isString(data)) { + data = JSON.parse(data); + } + data = data || {}; + if (lodash.isEmpty(data) || !data.email) return cb('Cannot set cards: no account to set'); + storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) { if (err) return cb(err); if (!allAccounts[email]) { return cb('Cannot set cards for unknown account ' + email); } - - allAccounts[email].cards = cards; - storage.set('bitpayAccounts-v2-' + network, allAccounts, cb); + bitpayAccounts = bitpayAccounts || {}; + bitpayAccounts[data.email] = bitpayAccounts[data.email] || {}; + bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data.cards; + storage.set('bitpayAccounts-v2-' + network, JSON.stringify(bitpayAccounts), cb); }); }; @@ -385,7 +393,6 @@ angular.module('copayApp.services') // email: account email // ] root.getBitpayDebitCards = function(network, cb) { - root.getBitpayAccounts(network, function(err, allAccounts) { if (err) return cb(err); @@ -510,6 +517,28 @@ angular.module('copayApp.services') }); }; + // account: { + // email: account email + // apiContext: the context needed for making future api calls + // bitpayDebitCards: an array of cards + // } + root.removeBitpayAccount = function(network, account, cb) { + if (lodash.isString(account)) { + account = JSON.parse(account); + } + account = account || {}; + if (lodash.isEmpty(account)) return cb('No account to remove'); + storage.get('bitpayAccounts-v3-' + network, function(err, bitpayAccounts) { + if (err) cb(err); + if (lodash.isString(bitpayAccounts)) { + bitpayAccounts = JSON.parse(bitpayAccounts); + } + bitpayAccounts = bitpayAccounts || {}; + delete bitpayAccounts[account.email]; + storage.set('bitpayAccounts-v3-' + network, JSON.stringify(bitpayAccounts), cb); + }); + }; + root.setAppIdentity = function(network, data, cb) { storage.set('appIdentity-' + network, data, cb); }; diff --git a/src/sass/views/bitpayCardIntro.scss b/src/sass/views/bitpayCardIntro.scss index a70a73105..bbb7d769e 100644 --- a/src/sass/views/bitpayCardIntro.scss +++ b/src/sass/views/bitpayCardIntro.scss @@ -1,4 +1,5 @@ #bitpayCard-intro { + @extend .deflash-blue; background: url(../img/onboarding-welcome-bg.png), linear-gradient(to bottom, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%); background-position: top center; background-size: contain; diff --git a/src/sass/views/bitpayCardPreferences.scss b/src/sass/views/bitpayServicesPreferences.scss similarity index 58% rename from src/sass/views/bitpayCardPreferences.scss rename to src/sass/views/bitpayServicesPreferences.scss index 7488f9964..11f0c43f3 100644 --- a/src/sass/views/bitpayCardPreferences.scss +++ b/src/sass/views/bitpayServicesPreferences.scss @@ -1,4 +1,4 @@ -#bitpayCardPreferences { +#bitpayServicesPreferences { .item { .item-title { display: block; @@ -14,9 +14,13 @@ &.item-icon-right { .icon-hotspot { right: 0px; - padding-left: 11px; - padding-right: 11px; - } + padding-left: 50px; + } + } + .icon-unlink { + background-image: url("../img/icon-unlink.svg"); + background-repeat: no-repeat; + background-position: center; } } } diff --git a/src/sass/views/includes/accountSelector.scss b/src/sass/views/includes/accountSelector.scss new file mode 100644 index 000000000..220361051 --- /dev/null +++ b/src/sass/views/includes/accountSelector.scss @@ -0,0 +1,79 @@ +account-selector { + + $border-color: #EFEFEF; + text-align: left; + + .bp-action-sheet__sheet { + padding-left: 2rem; + padding-right: .75rem; + } + + .account-selector { + .account { + border: 0; + padding-right: 0; + padding-top: 0; + padding-left: 65px; + padding-bottom: 0; + margin-bottom: 1px; + overflow: visible; + + > i { + padding: 0; + margin-left: -5px; + + > img { + height: 39px; + width: 39px; + padding: 4px; + background-color: $royal; + + &.icon-add { + background-color: $light-gray; + } + + } + } + } + .account-inner { + display: flex; + position: relative; + padding-top: 16px; + padding-bottom: 16px; + + &::after { + display: block; + position: absolute; + width: 100%; + height: 1px; + background: $border-color; + bottom: 0; + right: 0; + content: ''; + } + + .check { + padding: 0 1.2rem; + } + } + .account-details { + flex-grow: 1; + + .account-name { + padding-bottom: 5px; + } + + .account-email { + color: #3A3A3A; + font-family: "Roboto-Light"; + } + + .account-add { + padding-bottom: 16px; + padding-top: 11px; + } + + } + } + +} diff --git a/src/sass/views/tab-settings.scss b/src/sass/views/tab-settings.scss index b279fc3e9..41ce8df9a 100644 --- a/src/sass/views/tab-settings.scss +++ b/src/sass/views/tab-settings.scss @@ -1,8 +1,7 @@ .settings { @extend .deflash-blue; - .icon-bitpay-card { - background-image: url("../img/icon-card.svg"); - background-color: #1e3186; + .icon-bitpay { + background-image: url("../img/icon-bitpay.svg"); } .item { color: $dark-gray; diff --git a/src/sass/views/views.scss b/src/sass/views/views.scss index 2ced0150f..eaace7e9c 100644 --- a/src/sass/views/views.scss +++ b/src/sass/views/views.scss @@ -14,8 +14,8 @@ @import "advancedSettings"; @import "bitpayCard"; @import "bitpayCardIntro"; -@import "bitpayCardPreferences"; @import "buyandsell"; +@import "bitpayServicesPreferences"; @import "address-book"; @import "addresses"; @import "wallet-backup-phrase"; @@ -42,5 +42,6 @@ @import "includes/tx-status"; @import "includes/itemSelector"; @import "includes/walletSelector"; +@import "includes/accountSelector"; @import "integrations/integrations"; @import "custom-amount"; diff --git a/www/img/icon-account-add.svg b/www/img/icon-account-add.svg new file mode 100644 index 000000000..826fbec29 --- /dev/null +++ b/www/img/icon-account-add.svg @@ -0,0 +1,17 @@ + + + + Untitled + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/www/img/icon-account.svg b/www/img/icon-account.svg new file mode 100644 index 000000000..91c6e890f --- /dev/null +++ b/www/img/icon-account.svg @@ -0,0 +1,14 @@ + + + + Untitled + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/www/img/icon-unlink.svg b/www/img/icon-unlink.svg new file mode 100644 index 000000000..1330e50c3 --- /dev/null +++ b/www/img/icon-unlink.svg @@ -0,0 +1,26 @@ + + + + icon-link + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/views/bitpayCardIntro.html b/www/views/bitpayCardIntro.html index 3a972c86c..28d777d76 100644 --- a/www/views/bitpayCardIntro.html +++ b/www/views/bitpayCardIntro.html @@ -39,4 +39,11 @@ + + diff --git a/www/views/includes/accountSelector.html b/www/views/includes/accountSelector.html new file mode 100644 index 000000000..a8da1fb1e --- /dev/null +++ b/www/views/includes/accountSelector.html @@ -0,0 +1,34 @@ + + +
{{title}}
+ + + + + + + + + + + + + +
diff --git a/www/views/preferencesBitpayCard.html b/www/views/preferencesBitpayCard.html deleted file mode 100644 index 138327698..000000000 --- a/www/views/preferencesBitpayCard.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - BitPay Visa® Cards - - - -
-
- Cards -
-
- - xxxx-xxxx-xxxx-{{card.lastFourDigits}} - - - {{card.email}} - - -
-
-
-
diff --git a/www/views/preferencesBitpayServices.html b/www/views/preferencesBitpayServices.html new file mode 100644 index 000000000..7fe4a5994 --- /dev/null +++ b/www/views/preferencesBitpayServices.html @@ -0,0 +1,37 @@ + + + + + BitPay + + +
+
+ BitPay Visa® Cards +
+
+ + xxxx-xxxx-xxxx-{{card.lastFourDigits}} + + + {{card.email}} + + +
+
+
+
+ Accounts +
+
+ + {{account.firstName}} {{account.lastName}} + + + {{account.email}} + + +
+
+
+
diff --git a/www/views/tab-settings.html b/www/views/tab-settings.html index 3474e80fd..656287f3c 100644 --- a/www/views/tab-settings.html +++ b/www/views/tab-settings.html @@ -117,12 +117,12 @@ + ng-if="bitpayAccounts || (bitpayCardEnabled && bitpayCards)" + ui-sref="tabs.preferences.bitpayServices"> -
+
- BitPay Visa® Card + BitPay