mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-04 22:04:37 +01:00
Introduce QR Code View component (#2125)
* Introduce QR Code View component * more cleanup * fix js clipboard * Fix clipboard confirmation width calculation * fix tests Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
@@ -329,7 +329,7 @@ namespace BTCPayServer.Tests
|
||||
walletId ??= WalletId;
|
||||
GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = Driver.FindElement(By.Id("vue-address")).GetProperty("value");
|
||||
var addressStr = Driver.FindElement(By.Id("address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
|
||||
for (int i = 0; i < coins; i++)
|
||||
{
|
||||
|
||||
@@ -560,7 +560,7 @@ namespace BTCPayServer.Tests
|
||||
var walletId = new WalletId(storeId, "BTC");
|
||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
s.Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = s.Driver.FindElement(By.Id("vue-address")).GetProperty("value");
|
||||
var addressStr = s.Driver.FindElement(By.Id("address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)s.Server.NetworkProvider.GetNetwork("BTC")).NBitcoinNetwork);
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
for (int i = 0; i < 6; i++)
|
||||
@@ -761,13 +761,13 @@ namespace BTCPayServer.Tests
|
||||
//generate a receiving address
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
var receiveAddr = s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value");
|
||||
var receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
|
||||
//unreserve
|
||||
s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
|
||||
//generate it again, should be the same one as before as nothign got used in the meantime
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
|
||||
|
||||
//send money to addr and ensure it changed
|
||||
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
|
||||
@@ -779,8 +779,8 @@ namespace BTCPayServer.Tests
|
||||
await Task.Delay(200);
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
receiveAddr = s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value");
|
||||
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
|
||||
receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
|
||||
|
||||
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one
|
||||
s.GoToStore(storeId.storeId);
|
||||
@@ -789,7 +789,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
|
||||
|
||||
var invoiceId = s.CreateInvoice(storeId.storeName);
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="QRCoder" Version="1.4.1" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
|
||||
<PackageReference Include="DBriize" Version="1.0.1.3" />
|
||||
|
||||
22
BTCPayServer/Components/QRCode/QRCode.cs
Normal file
22
BTCPayServer/Components/QRCode/QRCode.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Drawing;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ViewComponents;
|
||||
using QRCoder;
|
||||
|
||||
namespace BTCPayServer.Components.QRCode
|
||||
{
|
||||
public class QRCode : ViewComponent
|
||||
{
|
||||
private static QRCodeGenerator qrGenerator = new QRCodeGenerator();
|
||||
|
||||
|
||||
public IViewComponentResult Invoke(string data)
|
||||
{
|
||||
|
||||
QRCodeData qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q);
|
||||
SvgQRCode qrCode = new SvgQRCode(qrCodeData);
|
||||
return new HtmlContentViewComponentResult(new HtmlString(qrCode.GetGraphic(new Size(256,256), "#000", "#f5f5f7", true, SvgQRCode.SizingMode.ViewBoxAttribute)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,65 +18,13 @@
|
||||
<link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.BootstrapUri)" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="@Context.Request.GetRelativePathOrAbsolute(themeManager.ThemeUri)" rel="stylesheet" asp-append-version="true" />
|
||||
<link href="~/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" asp-append-version="true" />
|
||||
<bundle name="wwwroot/bundles/lightning-node-info-bundle.min.js" asp-append-version="true" />
|
||||
<script type="text/javascript">
|
||||
var srvModel = @Safe.Json(Model);
|
||||
|
||||
window.onload = function() {
|
||||
Vue.use(Toasted);
|
||||
new Vue({
|
||||
el: '#app',
|
||||
components: {
|
||||
qrcode: VueQrcode
|
||||
},
|
||||
data: {
|
||||
srvModel: srvModel
|
||||
},
|
||||
mounted: function() {
|
||||
this.$nextTick(function() {
|
||||
var copyInput = new Clipboard('.copy');
|
||||
copyInput.on("success", function() {
|
||||
Vue.toasted.show('Copied', {
|
||||
iconPack: "fontawesome",
|
||||
icon: "copy",
|
||||
duration: 5000
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
<link href="~/main/qrcode.css" rel="stylesheet" asp-append-version="true" />
|
||||
<script src="~/js/copy-to-clipboard.js"></script>
|
||||
<script>
|
||||
window.onload = function (){
|
||||
document.querySelectorAll('[data-clipboard]').forEach(value => value.addEventListener('click', window.copyToClipboard));
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.qr-icon {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-container svg {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: copy;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="container">
|
||||
@@ -84,29 +32,32 @@
|
||||
<div class="col-md-8 col-sm-12 col-lg-6 mx-auto my-auto">
|
||||
<div class="card border-0">
|
||||
<div class="card-body p-4">
|
||||
<h1 class="card-title text-center" v-text="srvModel.storeName">@Model.StoreName</h1>
|
||||
<h1 class="card-title text-center">@Model.StoreName</h1>
|
||||
<h2 class="card-subtitle text-center text-secondary mb-2">
|
||||
<span v-text="srvModel.cryptoCode">@Model.CryptoCode</span>
|
||||
<span>@Model.CryptoCode</span>
|
||||
Lightning Node
|
||||
</h2>
|
||||
<h3 class="card-title text-center">
|
||||
<span v-text="srvModel.available ? 'Online' : 'Unavailable'">
|
||||
<span>
|
||||
@(Model.Available ? "Online" : "Unavailable")
|
||||
</span>
|
||||
<small class="text-@(Model.Available ? "success" : "danger")" :class="{ 'text-success': srvModel.available, 'text-danger': !srvModel.available }">
|
||||
<small class="text-@(Model.Available ? "success" : "danger")" >
|
||||
<span class="fa fa-circle"></span>
|
||||
</small>
|
||||
</h3>
|
||||
<div class="qr-container my-3" v-cloak v-if="srvModel.available">
|
||||
<img src="" alt="@Model.CryptoCode" class="qr-icon" :src="srvModel.cryptoImage" v-bind:alt="srvModel.cryptoCode" />
|
||||
<qrcode v-bind:value="srvModel.nodeInfo" :options="{ width: 256, margin: 1, color: {dark:'#000', light:'#f5f5f7'} }" tag="svg"></qrcode>
|
||||
</div>
|
||||
<div data-clipboard-target="#peer-info" class="input-group copy d-@(Model.Available ? "flex" : "none")" :class="{ 'd-flex': srvModel.available, 'd-none': !srvModel.available }">
|
||||
<input type="text" class="form-control" readonly="readonly" asp-for="NodeInfo" id="peer-info" :value="srvModel.nodeInfo" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text fa fa-copy py-2"></span>
|
||||
@if (Model.Available)
|
||||
{
|
||||
<div class="qr-container my-3">
|
||||
<img alt="@Model.CryptoCode" class="qr-icon" src="@Model.CryptoImage" />
|
||||
<vc:qr-code data="@Model.NodeInfo"> </vc:qr-code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group d-flex" data-clipboard="@Model.NodeInfo">
|
||||
<input type="text" class="form-control" style="cursor: copy" readonly="readonly" value="@Model.NodeInfo" id="peer-info"/>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary" data-clipboard-confirm>Copy node info</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||
@model BTCPayServer.Controllers.WalletReceiveViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
@@ -10,7 +9,7 @@
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-center">
|
||||
<partial name="_StatusMessage" />
|
||||
<partial name="_StatusMessage"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -20,7 +19,6 @@
|
||||
<div class="card-body">
|
||||
@if (string.IsNullOrEmpty(Model.Address))
|
||||
{
|
||||
|
||||
<h3 class="card-title mb-3">Receive @Model.CryptoCode</h3>
|
||||
<button id="generateButton" class="btn btn-lg btn-primary" type="submit" name="command" value="generate-new-address">Generate @Model.CryptoCode address</button>
|
||||
}
|
||||
@@ -30,7 +28,7 @@
|
||||
<noscript>
|
||||
<div class="card-body m-sm-0 p-sm-0">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control " readonly="readonly" asp-for="Address" id="address" />
|
||||
<input type="text" class="form-control " readonly="readonly" asp-for="Address" id="address"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text fa fa-copy"> </span>
|
||||
</div>
|
||||
@@ -47,14 +45,13 @@
|
||||
</noscript>
|
||||
<div class="only-for-js card-body m-sm-0 p-sm-0" id="app">
|
||||
<div class="qr-container mb-4">
|
||||
<img v-bind:src="srvModel.cryptoImage" class="qr-icon" />
|
||||
<qrcode v-bind:value="srvModel.address" :options="{ width: 256, margin: 1, color: {dark:'#000', light:'#f5f5f7'} }" tag="svg">
|
||||
</qrcode>
|
||||
<img src="@Model.CryptoImage" class="qr-icon"/>
|
||||
<vc:qr-code data="@Model.Address"/>
|
||||
</div>
|
||||
<div class="input-group copy" data-clipboard-target="#vue-address">
|
||||
<input type="text" class=" form-control " readonly="readonly" :value="srvModel.address" id="vue-address" />
|
||||
<div class="input-group" data-clipboard="@Model.Address">
|
||||
<input type="text" class="form-control" style="cursor: copy" readonly="readonly" value="@Model.Address" id="address"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text fa fa-copy"> </span>
|
||||
<button type="button" class="btn btn-outline-secondary" data-clipboard-confirm>Copy address</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
@@ -73,69 +70,12 @@
|
||||
</div>
|
||||
|
||||
@section HeadScripts
|
||||
|
||||
{
|
||||
<bundle name="wwwroot/bundles/lightning-node-info-bundle.min.js" asp-append-version="true" />
|
||||
<script type="text/javascript">
|
||||
var srvModel = @Safe.Json(Model);
|
||||
window.onload = function() {
|
||||
if($("#app").length <1){
|
||||
return;
|
||||
}
|
||||
Vue.use(Toasted);
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
components: {
|
||||
qrcode: VueQrcode
|
||||
},
|
||||
data: {
|
||||
srvModel: srvModel
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
this.$nextTick(function() {
|
||||
var copyInput = new Clipboard('.copy');
|
||||
|
||||
copyInput.on("success",
|
||||
function(e) {
|
||||
Vue.toasted.show('Copied',
|
||||
{
|
||||
iconPack: "fontawesome",
|
||||
icon: "copy",
|
||||
duration: 5000
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
<link href="~/main/qrcode.css" rel="stylesheet" asp-append-version="true"/>
|
||||
<script src="~/js/copy-to-clipboard.js"></script>
|
||||
<script>
|
||||
window.onload = function (){
|
||||
document.querySelectorAll('[data-clipboard]').forEach(value => value.addEventListener('click', window.copyToClipboard));
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.qr-icon {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-container svg {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: copy;
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
}
|
||||
|
||||
@@ -58,16 +58,6 @@
|
||||
"wwwroot/checkout/**/*.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/bundles/lightning-node-info-bundle.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/vendor/clipboard.js/clipboard.js",
|
||||
"wwwroot/vendor/jquery/jquery.js",
|
||||
"wwwroot/vendor/vuejs/vue.min.js",
|
||||
"wwwroot/vendor/vue-qrcode/vue-qrcode.min.js",
|
||||
"wwwroot/vendor/vue-toasted/vue-toasted.min.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/bundles/cart-bundle.min.js",
|
||||
"inputFiles": [
|
||||
|
||||
@@ -7,7 +7,7 @@ window.copyToClipboard = function (e, text) {
|
||||
var message = confirm.getAttribute('data-clipboard-confirm') || 'Copied ✔';
|
||||
if (!confirm.dataset.clipboardInitialText) {
|
||||
confirm.dataset.clipboardInitialText = confirm.innerText;
|
||||
confirm.style.minWidth = confirm.clientWidth + 'px';
|
||||
confirm.style.minWidth = confirm.getBoundingClientRect().width + 'px';
|
||||
}
|
||||
navigator.clipboard.writeText(data).then(function () {
|
||||
confirm.innerText = message;
|
||||
|
||||
20
BTCPayServer/wwwroot/main/qrcode.css
Normal file
20
BTCPayServer/wwwroot/main/qrcode.css
Normal file
@@ -0,0 +1,20 @@
|
||||
.qr-icon {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-container svg {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
Reference in New Issue
Block a user