Merge pull request #5552 from bitjson/feature/better-buy-and-sell

Improve Integrations UX (Buy & Sell, Amazon)
This commit is contained in:
Gustavo Maximiliano Cortez 2017-02-06 11:33:52 -03:00 committed by GitHub
commit a24ea66881
16 changed files with 278 additions and 111 deletions

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('coinbaseController', function($scope, $timeout, $ionicModal, $ionicHistory, $log, coinbaseService, lodash, platformInfo, ongoingProcess, popupService, externalLinkService) {
angular.module('copayApp.controllers').controller('coinbaseController', function($scope, $timeout, $ionicModal, $ionicHistory, $log, coinbaseService, lodash, platformInfo, ongoingProcess, popupService, externalLinkService, gettextCatalog) {
var isNW = platformInfo.isNW;
var isCordova = platformInfo.isCordova;
@ -9,7 +9,7 @@ angular.module('copayApp.controllers').controller('coinbaseController', function
$scope.currency = coinbaseService.getAvailableCurrency();
coinbaseService.getStoredToken(function(at) {
$scope.accessToken = at;
// Update Access Token if necessary
$scope.loading = true;
coinbaseService.init(function(err, data) {
@ -79,11 +79,35 @@ angular.module('copayApp.controllers').controller('coinbaseController', function
}
}
this.openSignupWindow = function() {
var url = coinbaseService.getSignupUrl();
var optIn = true;
var title = gettextCatalog.getString('Sign Up for Coinbase');
var message = gettextCatalog.getString('This will open Coinbase.com, where you can create an account.');
var okText = gettextCatalog.getString('Go to Coinbase');
var cancelText = gettextCatalog.getString('Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
}
this.openSupportWindow = function() {
var url = coinbaseService.getSupportUrl();
var optIn = true;
var title = gettextCatalog.getString('Coinbase Support');
var message = gettextCatalog.getString('You can email support@coinbase.com for direct support, or you can view their help center.');
var okText = gettextCatalog.getString('Open Help Center');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
}
this.getAuthenticateUrl = function() {
$scope.showOauthForm = isCordova || isNW ? false : true;
return coinbaseService.getOauthCodeUrl();
};
this.toggleOauthForm = function() {
$scope.showOauthForm = !$scope.showOauthForm;
}
this.submitOauthCode = function(code) {
var self = this;
ongoingProcess.set('connectingCoinbase', true);
@ -112,6 +136,7 @@ angular.module('copayApp.controllers').controller('coinbaseController', function
var self = this;
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.showOauthForm = false;
if (data.stateParams && data.stateParams.code) {
coinbaseService.getStoredToken(function(at) {
if (!at) self.submitOauthCode(data.stateParams.code);

View file

@ -114,7 +114,40 @@ angular.module('copayApp.controllers').controller('glideraController',
});
};
$scope.openAuthenticateWindow = function() {
$scope.openExternalLink($scope.getAuthenticateUrl());
$scope.showOauthForm = true
}
$scope.openLoginWindow = function() {
var glideraUrl = ($scope.network === 'testnet') ? 'https://sandbox.glidera.io/login' : 'https://glidera.io/login';
$scope.openExternalLink(glideraUrl);
}
$scope.openSupportWindow = function() {
var url = glideraService.getSupportUrl();
var optIn = true;
var title = gettextCatalog.getString('Glidera Support');
var message = gettextCatalog.getString('You can email glidera at support@glidera.io for direct support, or you can contact Glidera on Twitter.');
var okText = gettextCatalog.getString('Tweet @GlideraInc');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
}
$scope.retry = function() {
$scope.connectingGlidera = true;
$scope.update({'fullUpdate': true});
$timeout(function(){
$scope.connectingGlidera = false;
}, 300);
}
$scope.toggleOauthForm = function() {
$scope.showOauthForm = !$scope.showOauthForm;
}
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.showOauthForm = false;
initGlidera();
});

View file

@ -18,14 +18,14 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
var homeItem = {
name: 'amazon',
title: 'Amazon Gift Cards',
title: 'Amazon.com Gift Cards',
icon: 'icon-amazon',
sref: 'tabs.giftcards.amazon',
};
var nextStepItem = {
name: 'amazon',
title: 'Buy a gift card',
title: 'Buy Amazon.com Gift Cards',
icon: 'icon-amazon',
sref: 'tabs.giftcards.amazon',
};

View file

@ -1295,7 +1295,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
var nextStepItem = {
name: 'bitpaycard',
title: 'Add Bitpay VISA Card',
title: 'Add BitPay Visa® Card',
icon: 'icon-bitpay-card',
sref: 'tabs.bitpayCardIntro',
};

View file

@ -23,7 +23,7 @@ angular.module('copayApp.services').factory('buyAndSellService', function($log,
if (linkedServices.length == 0) {
nextStepsService.register({
title: 'Buy and Sell',
title: 'Buy or Sell Bitcoin',
name: 'buyandsell',
icon: 'icon-buy-bitcoin',
sref: 'tabs.buyandsell',

View file

@ -127,6 +127,14 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
return [amount, currency, amountUnitStr];
};
root.getSignupUrl = function() {
return credentials.HOST + '/signup';
}
root.getSupportUrl = function() {
return 'https://support.coinbase.com/';
}
root.getOauthCodeUrl = function() {
return credentials.HOST +
'/oauth/authorize?response_type=code&client_id=' +
@ -207,7 +215,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
root.isActive = function(cb) {
if (isWindowsPhoneApp)
if (isWindowsPhoneApp)
return cb();
if (lodash.isEmpty(credentials.CLIENT_ID))
@ -730,6 +738,7 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
buyAndSellService.register({
name: 'coinbase',
logo: 'img/coinbase-logo.png',
location: '33 Countries',
sref: 'tabs.buyandsell.coinbase',
configSref: 'tabs.preferences.coinbase',
linked: isActive,

View file

@ -49,6 +49,14 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
return credentials.NETWORK;
};
root.getSignupUrl = function() {
return credentials.HOST + '/register';
}
root.getSupportUrl = function() {
return 'https://twitter.com/GlideraInc';
}
root.getOauthCodeUrl = function() {
return credentials.HOST + '/oauth2/auth?response_type=code&client_id=' + credentials.CLIENT_ID + '&redirect_uri=' + credentials.REDIRECT_URI;
};
@ -325,6 +333,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
buyAndSellService.register({
name: 'glidera',
logo: 'img/glidera-logo.png',
location: 'US Only',
sref: 'tabs.buyandsell.glidera',
configSref: 'tabs.preferences.glidera',
linked: !!token,

View file

@ -46,6 +46,7 @@ $item-default-border: $subtle-gray;
$item-default-text: $dark-gray;
$item-default-active-bg: darken(#ffffff, 7%);
$item-default-active-border: darken($subtle-gray, 7%);
$item-divider-bg: $subtle-gray;
$bar-default-border: $subtle-gray;

View file

@ -0,0 +1,34 @@
#buy-and-sell {
.explain {
text-align: center;
margin-top: 1em;
}
.buy-and-sell-icon {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
box-shadow: $subtle-box-shadow;
background-color: #fa912b;
img {
height: 100%;
width: 100%;
}
}
.explain {
&-heading {
font-size: 20px;
margin: 1rem;
}
&-description {
margin-left: auto;
margin-right: auto;
opacity: .6;
max-width: 300px;
padding: 0 1em;
}
}
.item-note {
color: $light-gray;
}
}

View file

@ -9,7 +9,7 @@
stroke: #0067c8;
fill: #0067c8;
}
.add-bottom-for-cta {
bottom: 92px;
}
@ -73,7 +73,7 @@
color: $item-label-color;
margin-bottom: 8px;
}
.capitalized {
text-transform: capitalize;
}

View file

@ -0,0 +1,51 @@
@import "coinbase";
@import "glidera";
@import "amazon";
#coinbase, #glidera {
.button-small {
font-size: 13px;
}
}
.integration-onboarding {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
&-logo {
display: inline-block;
margin-bottom: 1em;
img {
max-width: 170px;
}
}
&-description {
margin-top: 0;
margin-left: 2rem;
margin-right: 2rem;
margin-bottom: 130px;
opacity: .6;
max-width: 300px;
}
&-cta, &-oauthform {
position: absolute;
bottom: 5vh;
width: 100%;
}
&-oauthform {
.item {
border: 0 none;
}
.item-floating-label {
padding-left: 0;
margin-left: 2em;
margin-right: 2em;
}
input {
border-bottom: 2px solid $light-gray;
}
}
}

View file

@ -15,6 +15,7 @@
@import "bitpayCard";
@import "bitpayCardIntro";
@import "bitpayCardPreferences";
@import "buyandsell";
@import "address-book";
@import "addresses";
@import "wallet-backup-phrase";
@ -41,7 +42,5 @@
@import "includes/tx-status";
@import "includes/itemSelector";
@import "includes/walletSelector";
@import "integrations/coinbase";
@import "integrations/glidera";
@import "integrations/amazon";
@import "integrations/integrations";
@import "custom-amount";

View file

@ -5,28 +5,29 @@
<ion-nav-title>Amazon.com Gift Cards</ion-nav-title>
</ion-nav-bar>
<ion-content>
<ion-content scroll="false" class="ng-hide" ng-show="!giftCards">
<div class="box-notification warning" ng-show="network == 'testnet'">
Sandbox version. Only for testing purpose
Sandbox version. Only for testing purpose.
</div>
<div class="integration-onboarding">
<div class="integration-onboarding-logo">
<img src="img/GCs-logo-cllb.png">
</div>
<div class="integration-onboarding-description" translate>Gift Cards are only redeemable on Amazon.com (US website). Cards never expire and can be redeemed towards millions of items.</div>
<div class="integration-onboarding-cta" ng-show="!showOauthForm">
<button class="button button-standard button-primary" ui-sref="tabs.giftcards.amazon.amount" translate>Buy a Gift Card</button>
<button class="button button-standard button-secondary" ng-click="openExternalLink('https://www.amazon.com')" translate>Visit Amazon.com &rarr;</button>
</div>
</div>
</ion-content>
<ion-content class="ng-hide" ng-show="giftCards">
<div class="box-notification warning" ng-show="network == 'testnet'">
Sandbox version. Only for testing purpose.
</div>
<div class="m20t text-center">
<img src="img/GCs-logo-cllb.png" alt="Amazon.com Gift Card" width="200">
<div class="size-12 m10t"><b>Only</b> redeemable on www.amazon.com (USA website)</div>
</div>
<div ng-if="!giftCards" class="m20t padding text-center">
<button class="button button-standard button-primary"
ui-sref="tabs.giftcards.amazon.amount">
Buy now
</button>
<div class="text-left m30v size-12">
Amazon.com Gift Cards never expire and can be redeemed towards millions of items at
<a ng-click="openExternalLink('https://www.amazon.com')">www.amazon.com</a>
</div>
<div class="size-12 m10t"><b>Only</b> redeemable on www.amazon.com (US website).</div>
</div>
<div class="m20t" ng-if="giftCards">

View file

@ -1,15 +1,23 @@
<ion-view>
<ion-view id="buy-and-sell">
<ion-nav-bar class="bar-royal">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title translate>Buy and sell</ion-nav-title>
<ion-nav-title translate>Buy or Sell Bitcoin</ion-nav-title>
</ion-nav-bar>
<ion-content>
<div class="list">
<div class="explain">
<i class="icon buy-and-sell-icon">
<img src="img/icon-bitcoin.svg"/>
</i>
<div class="explain-heading" translate>Connect an Exchange</div>
<div class="explain-description" translate>Buy or sell bitcoin directly from your wallet by connecting your exchange accounts.</div>
</div>
<div class="item item-divider"></div>
<div ng-repeat="service in services">
<div class="item item-icon-right" ui-sref="{{service.sref}}">
<img ng-src="{{service.logo}}" width="90">
<span class="item-note" translate>{{service.location}}</span>
<i class="icon bp-arrow-right"></i>
</div>
</div>

View file

@ -4,45 +4,46 @@
</ion-nav-back-button>
<ion-nav-title>Coinbase</ion-nav-title>
</ion-nav-bar>
<ion-content>
<div ng-if="!accessToken" ng-init="showOauthForm = false">
<div class="text-center m20v">
<img src="img/coinbase-logo.png" width="200">
<ion-nav-buttons side="secondary">
<button class="button button-clear button-small ng-hide" ng-show="!accessToken" ng-click="coinbase.toggleOauthForm()">
<span ng-hide="showOauthForm" translate>Enter Code</span>
<span ng-show="showOauthForm" translate>Restart</span>
</button>
</ion-nav-buttons>
<ion-content scroll="false" class="ng-hide" ng-show="!accessToken">
<div class="integration-onboarding">
<div class="integration-onboarding-logo">
<img src="img/coinbase-logo.png">
</div>
<div class="text-center small-10 small-centered columns" ng-show="!showOauthForm">
<p class="m20t text-gray size-12">Connect your Coinbase account to get started</p>
<button class="button button-standard button-primary"
ng-click="coinbase.openAuthenticateWindow()">
Connect to Coinbase
</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 class="integration-onboarding-description" ng-hide="showOauthForm" translate>Coinbase's exchange service is available in 33 countries, and can take 3-5 days to buy or sell bitcoin.</div>
<div class="integration-onboarding-description" ng-show="showOauthForm" translate>If you have trouble, contact Coinbase support for direct assistance.</div>
<div class="integration-onboarding-cta" ng-show="!showOauthForm">
<button class="button button-standard button-primary" ng-click="coinbase.openAuthenticateWindow()" translate>Connect Coinbase Account</button>
<button class="button button-standard button-secondary" ng-click="coinbase.openSignupWindow()" translate>Sign Up for Coinbase</button>
</div>
<div ng-show="showOauthForm">
<div ng-show="showOauthForm" class="integration-onboarding-oauthform">
<form name="oauthCodeForm" ng-submit="coinbase.submitOauthCode(code)" novalidate>
<div class="list settings-input-group">
<label class="item item-input item-stacked-label">
<div class="list">
<label class="item item-input item-floating-label">
<span class="input-label">OAuth Code</span>
<input type="text"
ng-model="code"
ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
placeholder="{{'Enter OAuth Code'}}" required>
</label>
</div>
<input
class="button button-standard button-primary"
type="submit" value="Connect Coinbase Account" ng-disabled="oauthCodeForm.$invalid">
<button type="button" class="button button-standard button-secondary" ng-click="coinbase.openSupportWindow()" translate>Coinbase Support &rarr;</button>
</form>
</div>
</div>
</ion-content>
<div ng-if="accessToken">
<ion-content class="ng-hide" ng-show="accessToken">
<div>
<div class="m20t text-center" ng-click="updateTransactions()">
<img src="img/coinbase-logo.png" width="200">
@ -52,7 +53,7 @@
<ion-spinner class="spinner-dark recent" icon="crescent" ng-show="!buyPrice || !sellPrice"></ion-spinner>
<span ng-show="buyPrice && sellPrice">
{{buyPrice.amount}} {{buyPrice.currency}}
|
|
{{sellPrice.amount}} {{sellPrice.currency}}
</span>
</div>
@ -77,7 +78,7 @@
<div class="list card">
<div class="item item-heading" ng-click="updateTransactions()">
Activity
</div>
</div>
<a class="item"
ng-if="pendingTransactions.data && !error"
ng-repeat="(id, tx) in pendingTransactions.data | orderObjectBy:'updated_at':true track by $index"

View file

@ -3,72 +3,68 @@
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title>Glidera</ion-nav-title>
</ion-nav-bar>
<ion-content ng-show="!connectingGlidera">
<div class="box-notification error m0" ng-show="!network">
Glidera is disabled for this application
</div>
<div class="box-notification warning m0" ng-show="network == 'testnet'">
Testnet wallets only work with Glidera Sandbox Accounts
</div>
<div ng-if="!token">
<div ng-init="showOauthForm = false">
<div class="text-center m20v">
<img src="img/glidera-logo.png" ng-click="update({'fullUpdate': true})" width="200">
<ion-nav-buttons side="secondary">
<button class="button button-clear button-small ng-hide" ng-show="!token && !connectingGlidera" ng-click="toggleOauthForm()">
<span ng-hide="showOauthForm" translate>Enter Code</span>
<span ng-show="showOauthForm" translate>Restart</span>
</button>
<button class="button button-clear button-small ng-hide" ng-show="token && !connectingGlidera && status && !status.userCanTransact" ng-click="retry()" translate>
Refresh
</button>
</ion-nav-buttons>
<ion-content scroll="false" class="ng-hide" ng-show="!token && !connectingGlidera">
<div class="box-notification error m0" ng-show="!network">
The Glidera integration is currently disabled.
</div>
<div class="box-notification warning m0" ng-show="network == 'testnet'">
Testnet wallets only work with Glidera Sandbox Accounts.
</div>
<div class="integration-onboarding">
<div class="integration-onboarding-logo">
<img src="img/glidera-logo.png">
</div>
<div class="text-center small-10 small-centered columns" ng-show="!showOauthForm">
<p class="glidera-lead">You can buy and sell Bitcoin with a US bank account directly in this app.</p>
<p class="glidera-text">Connect your Glidera account to get started.</p>
<button class="button button-standard button-primary"
ng-click="openExternalLink(getAuthenticateUrl()); showOauthForm = true">
Connect to Glidera
</button>
<div class="m10t">
<a href ng-click="showOauthForm = true" class="button button-standard button-secondary buttion-clear">
Have the OAuth Code?
</a>
</div>
<div class="disclosure">
<p>Glidera Inc. (Glidera) is providing the service of buying or selling bitcoin to BitPay users. To enable this service, Glidera has registered with US Treasury Departments FinCEN as a Money Service Business (#31000042625755). Users of BitPay must agree to the service agreement presented by Glidera prior to obtaining Glideras service of buying or selling bitcoin.</p>
<p>Service is available in the U.S. and Canada.</p>
</div>
<div class="integration-onboarding-description" ng-hide="showOauthForm" translate>Glidera's exchange service is available in the United States, and can take 1-2 weeks to buy or sell bitcoin.</div>
<div class="integration-onboarding-description" ng-show="showOauthForm" translate>If you have trouble, contact Glidera support for direct assistance.</div>
<div class="integration-onboarding-cta" ng-show="!showOauthForm">
<button class="button button-standard button-primary" ng-click="openAuthenticateWindow()" translate>Connect to Glidera</button>
<button type="button" class="button button-standard button-secondary" ng-click="openSupportWindow()" translate>Glidera Support &rarr;</button>
</div>
<div ng-show="showOauthForm">
<form class="settings" name="oauthCodeForm" ng-submit="submitOauthCode(code)" novalidate>
<div class="list settings-input-group">
<label class="item item-input item-stacked-label">
<div ng-show="showOauthForm" class="integration-onboarding-oauthform">
<form name="oauthCodeForm" ng-submit="submitOauthCode(code)" novalidate>
<div class="list">
<label class="item item-input item-floating-label">
<span class="input-label">OAuth Code</span>
<input type="text"
ng-model="code"
ng-attr-placeholder="{{'Paste the authorization code here'}}" required>
placeholder="{{'Enter OAuth Code'}}" required>
</label>
</div>
<input class="button button-standard button-primary" type="submit" value="Connect Glidera Account" ng-disabled="oauthCodeForm.$invalid">
<input
class="button button-standard button-primary"
type="submit" value="Connect Glidera Account" ng-disabled="oauthCodeForm.$invalid">
<button type="button" class="button button-standard button-secondary" ng-click="openSupportWindow()" translate>Glidera Support &rarr;</button>
</form>
</div>
</div>
</div>
</ion-content>
<div ng-if="token">
<div class="text-center m20v">
<img src="img/glidera-logo.png" ng-click="update({'fullUpdate': true})" width="200">
<ion-content scroll="false" class="ng-hide" ng-show="token && !connectingGlidera && status && !status.userCanTransact">
<div class="integration-onboarding">
<div class="integration-onboarding-logo">
<img src="img/glidera-logo.png">
</div>
<div class="integration-onboarding-description" translate>Please complete your account verification on Glidera's website. If you have trouble, contact Glidera's support for direct assistance.</div>
<div class="integration-onboarding-cta">
<button class="button button-standard button-primary" ng-click="openLoginWindow()" translate>Complete Glidera Setup</button>
<button type="button" class="button button-standard button-secondary" ng-click="openSupportWindow()" translate>Glidera Support &rarr;</button>
</div>
</div>
<div class="text-center m30v size-12"
ng-show="status && !status.userCanTransact">
<h4 class="text-bold">Complete Setup</h4>
<div>Your Glidera account is not ready to transact. Please, verify it at <b>Glidera.io</b></div>
<a class="button"
ng-init="glideraUrl = network == 'testnet' ? 'https://sandbox.glidera.io/login' : 'https://glidera.io/login'"
ng-click="openExternalLink(glideraUrl)">
Go to Glidera
</a>
</ion-content>
<ion-content class="ng-hide" ng-show="token && !connectingGlidera && status && status.userCanTransact">
<div class="text-center m20v">
<img src="img/glidera-logo.png" width="170">
</div>
<div class="list card"