From 29d5d708701a9934c98a26a8aa86cb4b8416d9ac Mon Sep 17 00:00:00 2001 From: blavenie <benoit.lavenier@e-is.pro> Date: Tue, 22 Aug 2017 11:34:48 +0200 Subject: [PATCH] [enh] Finish payment API (doc + transfer form) - fix #527 --- www/i18n/locale-en-GB.json | 59 +++++++++++ www/i18n/locale-en.json | 59 +++++++++++ www/i18n/locale-fr-FR.json | 23 +++- www/js/api/app.js | 164 ++++++++++++++++++++++++----- www/js/config.js | 2 +- www/js/services/utils-services.js | 2 +- www/js/services/wallet-services.js | 6 ++ www/templates/api/doc.html | 6 +- www/templates/api/transfer.html | 15 ++- 9 files changed, 297 insertions(+), 39 deletions(-) diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json index c19c8e568..0de122a39 100644 --- a/www/i18n/locale-en-GB.json +++ b/www/i18n/locale-en-GB.json @@ -763,5 +763,64 @@ "END_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>Welcome to the <b>free economy</b>!", "END_NOT_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>If you wish to join the currency {{currency|capitalize}}, simply click <b>{{'LOGIN.CREATE_ACCOUNT'|translate}}</b> below." } + }, + "API" :{ + "COMMON": { + "LINK_DOC": "API documentation", + "LINK_DOC_HELP": "API documentation for developers" + }, + "HOME": { + "TITLE": "{{'COMMON.APP_NAME'|translate}} API Documentation", + "MESSAGE": "Welcome to the {{'COMMON.APP_NAME'|translate}} <b>API documentation </b>.<br/>Connect your web site to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!", + "MESSAGE_SHORT": "Connect your websites to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!", + "DOC_HEADER": "Available services:" + }, + "TRANSFER": { + "TITLE": "{{'COMMON.APP_NAME'|translate}} - Online payment", + "TITLE_SHORT": "Online payment", + "SUMMARY": "Order summary:", + "AMOUNT": "Amount:", + "NAME": "Recipient:", + "PUBKEY": "Public key of the recipient:", + "COMMENT": "Order reference:", + + "DEMO": { + "SALT": "demo", + "PASSWORD": "demo", + "PUBKEY": "3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1", + "HELP": "<b>Demonstration mode</b>: No payment will actually be sent during this simulation.<br/>Please use credentials: <b>{{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}</b>", + "BAD_CREDENTIALS": "Invalid credentials.<br/>In demonstration mode, credentials should be: {{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}" + }, + "INFO": { + "SUCCESS_REDIRECTING_WITH_NAME": "Payment sent.<br/>Redirect to <b>{{name}}</b>...", + "SUCCESS_REDIRECTING": "Payment sent.<br/>Redirect to the seller's website...", + "CANCEL_REDIRECTING_WITH_NAME": "Payment cancelled.<br/>Redirect to <b>{{name}}</b>...", + "CANCEL_REDIRECTING": "Payment cancelled.<br/>Redirect to the seller's website..." + }, + "ERROR": { + "TRANSFER_FAILED": "Payment failed" + } + }, + "DOC": { + "BTN_DEMO": "Try", + "DESCRIPTION_DIVIDER": "Description", + "URL_DIVIDER": "Access URL", + "PARAMETERS_DIVIDER": "Parameters", + "AVAILABLE_PARAMETERS": "Here is the list of parameters and options:", + "SUCCEED": "<i class=\"icon ion-checkmark\"></i> Success!", + "CANCELLED": "<i class=\"icon ion-close\"></i> Canceled by user", + "RESULT": "Result returned by call:", + "TRANSFER": { + "TITLE": "Payments", + "DESCRIPTION": "From a site (eg online marketplace) you can delegate payment in free currency to Cesium API. To do this, simply open a page at the following address:", + "PARAM_PUBKEY_HELP": "Recipient's public key (required)", + "PARAM_AMOUNT": "Amount", + "PARAM_AMOUNT_HELP": "Transaction amount (required)", + "PARAM_COMMENT_HELP": "Reference or comment. You will allow for example to identify the payment in the BlockChain.", + "PARAM_NAME_HELP": "The name of your website. This can be a readable name (eg \"My online site\"), or a web address (eg \"www.MySite.com\").", + "PARAM_REDIRECT_URL_HELP": "Redirect URL. Can contain the following strings, which will be replaced by the values of the transaction: \"{tx}\", \"{hash}\", \"{comment}\" and \"{amount}\".", + "PARAM_CANCEL_URL_HELP": "URL in case of cancellation" + } + } } } diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json index 6c77fc94e..aadb06afa 100644 --- a/www/i18n/locale-en.json +++ b/www/i18n/locale-en.json @@ -763,5 +763,64 @@ "END_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>Welcome to the <b>free economy</b>!", "END_NOT_LOGIN": "This guided visit has <b>ended</b>.<br/><br/>If you wish to join the currency {{currency|capitalize}}, simply click <b>{{'LOGIN.CREATE_ACCOUNT'|translate}}</b> below." } + }, + "API" :{ + "COMMON": { + "LINK_DOC": "API documentation", + "LINK_DOC_HELP": "API documentation for developers" + }, + "HOME": { + "TITLE": "{{'COMMON.APP_NAME'|translate}} API Documentation", + "MESSAGE": "Welcome to the {{'COMMON.APP_NAME'|translate}} <b>API documentation </b>.<br/>Connect your web site to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!", + "MESSAGE_SHORT": "Connect your websites to <a href=\"http://duniter.org\" target=\"_system\">Duniter</a> very easily!", + "DOC_HEADER": "Available services:" + }, + "TRANSFER": { + "TITLE": "{{'COMMON.APP_NAME'|translate}} - Online payment", + "TITLE_SHORT": "Online payment", + "SUMMARY": "Order summary:", + "AMOUNT": "Amount:", + "NAME": "Recipient:", + "PUBKEY": "Public key of the recipient:", + "COMMENT": "Order reference:", + + "DEMO": { + "SALT": "demo", + "PASSWORD": "demo", + "PUBKEY": "3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1", + "HELP": "<b>Demonstration mode</b>: No payment will actually be sent during this simulation.<br/>Please use credentials: <b>{{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}</b>", + "BAD_CREDENTIALS": "Invalid credentials.<br/>In demonstration mode, credentials should be: {{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}" + }, + "INFO": { + "SUCCESS_REDIRECTING_WITH_NAME": "Payment sent.<br/>Redirect to <b>{{name}}</b>...", + "SUCCESS_REDIRECTING": "Payment sent.<br/>Redirect to the seller's website...", + "CANCEL_REDIRECTING_WITH_NAME": "Payment cancelled.<br/>Redirect to <b>{{name}}</b>...", + "CANCEL_REDIRECTING": "Payment cancelled.<br/>Redirect to the seller's website..." + }, + "ERROR": { + "TRANSFER_FAILED": "Payment failed" + } + }, + "DOC": { + "BTN_DEMO": "Try", + "DESCRIPTION_DIVIDER": "Description", + "URL_DIVIDER": "Access URL", + "PARAMETERS_DIVIDER": "Parameters", + "AVAILABLE_PARAMETERS": "Here is the list of parameters and options:", + "SUCCEED": "<i class=\"icon ion-checkmark\"></i> Success!", + "CANCELLED": "<i class=\"icon ion-close\"></i> Canceled by user", + "RESULT": "Result returned by call:", + "TRANSFER": { + "TITLE": "Payments", + "DESCRIPTION": "From a site (eg online marketplace) you can delegate payment in free currency to Cesium API. To do this, simply open a page at the following address:", + "PARAM_PUBKEY_HELP": "Recipient's public key (required)", + "PARAM_AMOUNT": "Amount", + "PARAM_AMOUNT_HELP": "Transaction amount (required)", + "PARAM_COMMENT_HELP": "Reference or comment. You will allow for example to identify the payment in the BlockChain.", + "PARAM_NAME_HELP": "The name of your website. This can be a readable name (eg \"My online site\"), or a web address (eg \"www.MySite.com\").", + "PARAM_REDIRECT_URL_HELP": "Redirect URL. Can contain the following strings, which will be replaced by the values of the transaction: \"{tx}\", \"{hash}\", \"{comment}\" and \"{amount}\".", + "PARAM_CANCEL_URL_HELP": "URL in case of cancellation" + } + } } } diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index ecf9836d3..0ffe7469c 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -783,6 +783,20 @@ "NAME": "Destinataire :", "PUBKEY": "Clé publique du destinaire :", "COMMENT": "Référence de la commande :", + + "DEMO": { + "SALT": "demo", + "PASSWORD": "demo", + "PUBKEY": "3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1", + "HELP": "<b>Mode démonstration</b> : Aucun paiement ne sera réellement envoyé pendant cette simulation.<br/>Veuillez utiliser les identifiants : <b>{{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}</b>", + "BAD_CREDENTIALS": "Vérifiez votre saisie.<br/>En mode démonstration, les identifiants sont : {{'API.TRANSFER.DEMO.SALT'|translate}} / {{'API.TRANSFER.DEMO.PASSWORD'|translate}}" + }, + "INFO": { + "SUCCESS_REDIRECTING_WITH_NAME": "Paiement envoyé.<br/>Redirection vers <b>{{name}}</b>...", + "SUCCESS_REDIRECTING": "Paiement envoyé.<br/>Redirection vers le site du vendeur...", + "CANCEL_REDIRECTING_WITH_NAME": "Paiement annulé.<br/>Redirection vers <b>{{name}}</b>...", + "CANCEL_REDIRECTING": "Paiement annulé.<br/>Redirection vers le site du vendeur..." + }, "ERROR": { "TRANSFER_FAILED": "Echec du paiement" } @@ -791,19 +805,20 @@ "BTN_DEMO": "Démo", "DESCRIPTION_DIVIDER": "Description", "URL_DIVIDER": "URL d'accès", - "PARAMETERS_DIVIDER": "Options", - "AVAILABLE_PARAMETERS": "Voici la liste des options possibles :", + "PARAMETERS_DIVIDER": "Paramètres", + "AVAILABLE_PARAMETERS": "Voici la liste des paramètres et options :", "SUCCEED": "<i class=\"icon ion-checkmark\"></i> Succès !", "CANCELLED": "<i class=\"icon ion-close\"></i> Annulé par l'utilisateur", "RESULT": "Résultat retourné par l'appel :", "TRANSFER": { "TITLE": "Paiements", "DESCRIPTION": "Depuis un site (ex: vente en ligne) vous pouvez déléguer le paiement en monnaie libre à Cesium API. Pour cela, il vous suffit de déclencher l'ouveture d'un page sur l'adresse suivante :", + "PARAM_PUBKEY_HELP": "Clé publique du destinataire (obligatoire)", "PARAM_AMOUNT": "Montant", "PARAM_AMOUNT_HELP": "Montant de la transaction (obligatoire)", "PARAM_COMMENT_HELP": "Référence ou commentaire. Vous permettra par exemple d'identifier le paiement dans la BlockChain.", - "PARAM_NAME_HELP": "Le nom de votre site. Cela peut-etre un nom lisible (\"Mon site en ligne\"), ou encore une pseudo-adresse web (\"MonSite.com\").", - "PARAM_REDIRECT_URL_HELP": "URL de redirection", + "PARAM_NAME_HELP": "Le nom de votre site web. Cela peut-etre un nom lisible (\"Mon site en ligne\"), ou encore une pseudo-adresse web (\"MonSite.com\").", + "PARAM_REDIRECT_URL_HELP": "URL de redirection. Peut contenir les chaines suivantes, qui seront remplacée par les valeurs de la transaction : \"{tx}\", \"{hash}\", \"{comment}\" et \"{amount}\".", "PARAM_CANCEL_URL_HELP": "URL en cas d'annulation" } } diff --git a/www/js/api/app.js b/www/js/api/app.js index 255c37840..4331715a2 100644 --- a/www/js/api/app.js +++ b/www/js/api/app.js @@ -42,6 +42,7 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr }) .state('api.transfer', { + cache: false, url: "/payment/:pubkey?name&amount&udAmount&comment&redirect_url&cancel_url&demo", views: { 'menuContent': { @@ -78,8 +79,8 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr comment: 'REF-0111', name: 'mon-site.com', pubkey: 'G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU', - redirect_url: $rootScope.rootPath + '#/app/home?result={comment}&service=payment', - cancel_url: $rootScope.rootPath + '#/app/home?cancel&service=payment' + redirect_url: $rootScope.rootPath + '#/app/home?service=payment&result={comment}%0A{hash}%0A{tx}', + cancel_url: $rootScope.rootPath + '#/app/home?service=payment&cancel' }; $scope.result = {}; @@ -100,7 +101,7 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr }) .controller('ApiTransferCtrl', function ($scope, $rootScope, $timeout, $controller, $state, $q, $translate, $filter, - BMA, UIUtils, csCurrency, csTx, csWallet){ + BMA, CryptoUtils, UIUtils, csCurrency, csTx, csWallet){ 'ngInject'; // Initialize the super class and extend it. @@ -141,27 +142,74 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr }; $scope.$on('$ionicView.enter', $scope.enter); - function createDemoWallet() { + function createDemoWallet(authData) { + var demoPubkey = '3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1'; + return { start: function() { return $q.when(); }, login: function() { - return $q.when({ - uid: 'Demo', - pubkey: 'fakeDemoPublicKey' - }); + var self = this; + return $translate('API.TRANSFER.DEMO.PUBKEY') + .then(function(pubkey) { + if (!authData || authData.pubkey != '3G28bL6deXQBYpPBpLFuECo46d3kfYMJwst7uhdVBnD1') { + throw {message: 'API.TRANSFER.DEMO.BAD_CREDENTIALS'}; + } + self.data = { + keypair: authData.keypair + }; + return { + uid: 'Demo', + pubkey: demoPubkey + }; + }); }, transfer: function() { - return $q.when(); + var self = this; + return BMA.blockchain.current() + .then(function(block) { + var tx = 'Version: '+ BMA.constants.PROTOCOL_VERSION +'\n' + + 'Type: Transaction\n' + + 'Currency: ' + block.currency + '\n' + + 'Blockstamp: ' + block.number + '-' + block.hash + '\n' + + 'Locktime: 0\n' + // no lock + 'Issuers:\n' + + demoPubkey + '\n' + + 'Inputs:\n' + + [$scope.transferData.amount, block.unitbase, 'T', 'FakeId27jQMAf3jqL2fr75ckZ6Jgi9TZL9fMf9TR9vBvG', 0].join(':')+ '\n' + + 'Unlocks:\n' + + '0:SIG(0)\n' + + 'Outputs:\n' + + [$scope.transferData.amount, block.unitbase, 'SIG(' + $scope.transferData.pubkey + ')'].join(':')+'\n' + + 'Comment: '+ ($scope.transferData.comment||'') + '\n'; + + return CryptoUtils.sign(tx, self.data.keypair) + .then(function(signature) { + var signedTx = tx + signature + "\n"; + return CryptoUtils.util.hash(signedTx) + .then(function(txHash) { + return $q.when({ + tx: signedTx, + hash: txHash + }); + }); + }) + }); } }; } function onLogin(authData) { - if (!authData) return; - var wallet = $scope.demo ? createDemoWallet() : csWallet.instance('api', BMA); + // User cancelled + if (!authData) return $scope.onCancel(); + + // Avoid multiple click + if ($scope.sending) return; + $scope.sending = true; + + var wallet = $scope.demo ? createDemoWallet(authData) : csWallet.instance('api', BMA); UIUtils.loading.show(); @@ -177,33 +225,37 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr }) .then(function(confirm) { if (!confirm) return; + // sent transfer return UIUtils.loading.show() .then(function(){ var amount = parseInt($scope.transferData.amount); // remove 2 decimals on quantitative mode return wallet.transfer($scope.transferData.pubkey, amount, $scope.transferData.comment, false /*always quantitative mode*/); }) - .then(function() { + .then(function(txRes) { + UIUtils.loading.hide(); - return true; // sent + return txRes; }) - .catch(UIUtils.onError('API.TRANSFER.ERROR.TRANSFER_FAILED')); + .catch(function(err) { + UIUtils.onError()(err); + return false; + }); }) - .then(function(sent) { - if (sent && $scope.transferData.redirect_url) { - return $timeout(function () { - // if iframe: send to parent - if (window.top && window.top.location) { - window.top.location.href = $scope.transferData.redirect_url; - } - else if (parent && parent.document && parent.document.location) { - parent.document.location.href = $scope.transferData.redirect_url; - } - else { - window.location.assign($scope.transferData.redirect_url); - } - }, 3000); + .then(function(txRes) { + if (txRes) { + return $scope.onSuccess(txRes); + } + else { + $scope.sending = false; + } + }) + .catch(function(err){ + if (err && err === 'CANCELLED') { + return $scope.onCancel(); } + $scope.sending = false; + UIUtils.onError()(err); }); } @@ -222,6 +274,62 @@ angular.module('cesium-api', ['ionic', 'ionic-material', 'ngMessages', 'pascalpr .then(UIUtils.alert.confirm); }; + $scope.onSuccess = function(txRes) { + if (!$scope.transferData.redirect_url) { + return UIUtils.toast.show('INFO.TRANSFER_SENT'); + } + + return ($scope.transferData.name ? $translate('API.TRANSFER.INFO.SUCCESS_REDIRECTING_WITH_NAME', $scope.transferData) : $translate('API.TRANSFER.INFO.SUCCESS_REDIRECTING')) + .then(function(message){ + return UIUtils.loading.show({template: message}); + }) + .then(function() { + var url = $scope.transferData.redirect_url; + // Make replacements + url = url.replace(/\{hash\}/g, txRes.hash||''); + url = url.replace(/\{comment\}/g, $scope.transferData.comment||''); + url = url.replace(/\{amount\}/g, $scope.transferData.amount.toString()); + url = url.replace(/\{tx\}/g, encodeURI(txRes.tx)); + + return $scope.redirectToUrl(url, 2500); + }); + }; + + $scope.onCancel = function() { + if (!$scope.transferData.cancel_url) { + // TODO: clean form ? + $scope.formData.salt = undefined; + $scope.formData.password = undefined; + return; // nothing to do + } + + return ($scope.transferData.name ? $translate('API.TRANSFER.INFO.CANCEL_REDIRECTING_WITH_NAME', $scope.transferData) : $translate('API.TRANSFER.INFO.CANCEL_REDIRECTING')) + .then(function(message){ + return UIUtils.loading.show({template: message}); + }) + .then(function() { + return $scope.redirectToUrl($scope.transferData.cancel_url, 1500); + }); + }; + + $scope.redirectToUrl = function(url, timeout) { + if (!url) return; + + return $timeout(function() { + // if iframe: send to parent + if (window.top && window.top.location) { + window.top.location.href = url; + } + else if (parent && parent.document && parent.document.location) { + parent.document.location.href = url; + } + else { + window.location.assign(url); + } + return UIUtils.loading.hide(); + }, timeout||0); + }; + /* -- methods need by Login controller -- */ $scope.setForm = function(form) { diff --git a/www/js/config.js b/www/js/config.js index 60dfb25af..25377cb55 100644 --- a/www/js/config.js +++ b/www/js/config.js @@ -67,7 +67,7 @@ angular.module("cesium.config", []) } }, "version": "0.16.1", - "build": "2017-08-21T21:55:39.967Z", + "build": "2017-08-22T06:59:53.332Z", "newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug" }) diff --git a/www/js/services/utils-services.js b/www/js/services/utils-services.js index f99cba37f..588bf2db5 100644 --- a/www/js/services/utils-services.js +++ b/www/js/services/utils-services.js @@ -129,7 +129,7 @@ angular.module('cesium.utils.services', []) return $translate('COMMON.LOADING') .then(function(translation){ loadingTextCache = translation; - return showLoading(); + return showLoading(options); }); } options = options || {}; diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index b0d835508..891cf66a1 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -936,6 +936,11 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se // API extension api.data.raise.balanceChanged(data); api.data.raise.newTx(data); + + // Return TX hash (if chained TXs, return the last tx hash) - required by Cesium-API + return { + hash: res.hash + }; }); }) .catch(function(err) { @@ -1133,6 +1138,7 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se output.pending = true; }); return { + tx: signedTx, hash: txHash, sources: newSources }; diff --git a/www/templates/api/doc.html b/www/templates/api/doc.html index 373bb0cf6..9bb07912c 100644 --- a/www/templates/api/doc.html +++ b/www/templates/api/doc.html @@ -30,6 +30,10 @@ <div class="item item-text-wrap"> <p translate>API.DOC.AVAILABLE_PARAMETERS</p> + <div class="row"> + <div class="col col-20 text-italic">pubkey</div> + <div class="col" translate>API.DOC.TRANSFER.PARAM_PUBKEY_HELP</div> + </div> <div class="row"> <div class="col col-20 text-italic">amount</div> <div class="col" translate>API.DOC.TRANSFER.PARAM_AMOUNT_HELP</div> @@ -61,7 +65,7 @@ <div class="item item-text-wrap" ng-if="result.type === 'payment' && !result.cancelled"> <h2 class="text-right balanced" translate>API.DOC.SUCCEED</h2> <h4 class="gray" translate>API.DOC.RESULT</h4> - <p class="balanced-100-bg padding dark"> {{result.content}}</p> + <p class="balanced-100-bg padding dark text-keep-lines">{{result.content}}</p> </div> <div class="item item-text-wrap" ng-if="result.type === 'payment' && result.cancelled"> <h2 class="text-right assertive" translate>API.DOC.CANCELLED</h2> diff --git a/www/templates/api/transfer.html b/www/templates/api/transfer.html index 01fc26567..ee0d5c8c8 100644 --- a/www/templates/api/transfer.html +++ b/www/templates/api/transfer.html @@ -16,7 +16,7 @@ </button> </ion-nav-buttons> - <ion-content class=" has-header no-padding-xs positive-900-bg text-center"> + <ion-content class=" has-header no-padding-xs positive-900-bg"> <br class="hidden-xs"/> @@ -28,11 +28,18 @@ <div class="light-bg"> - <h2 class="padding-top"> + <h2 class="padding-top text-center"> <span class="hidden-xs" translate>API.TRANSFER.TITLE</span> <span class="visible-xs" translate>API.TRANSFER.TITLE_SHORT</span> </h2> + <div class="no-padding energized-100-bg" ng-if="demo"> + <div class="item item-icon-left item-text-wrap no-border"> + <i class="icon ion-information-circled positive" ></i> + <p translate>API.TRANSFER.DEMO.HELP</p> + </div> + </div> + <ng-include src="'templates/login/form_login.html'"></ng-include> </div> @@ -78,12 +85,12 @@ </div> </div> - <p class="visible-xs visible-sm light padding-top"> + <p class="visible-xs visible-sm light padding-top text-center"> {{'COMMON.APP_NAME'|translate}} - <a href="#" ng-click="showAboutModal($event)">v{{$root.config.version}}</a> </p> - <p class="hidden-xs hidden-sm gray padding-top"> + <p class="hidden-xs hidden-sm gray padding-top text-center"> {{'COMMON.APP_NAME'|translate}} v{{$root.config.version}} - <a href="#" ng-click="showAboutModal($event)" title="{{'HOME.BTN_ABOUT'|translate}}">{{'HOME.BTN_ABOUT'|translate}}</a> - <a ui-sref="app.home" target="_blank" title="{{'API.COMMON.LINK_DOC_HELP'|translate}}">{{'API.COMMON.LINK_DOC'|translate}}</a> -- GitLab