Redesign Wallet UI (#3441)

* Update wallet navigation

* Find matching text color for label bg color

* Cleanup

* Extract WalletNav component

* Move PSBT link to Send and Rescan link to Settings

* Update transactions view

* Test fixes

* Adapt invoices list actions

* Show invoice actions only if there are any invoices

* Link wallet name and balance to tranactions list

* Move wallet related actions from list to settings

* Fix main menu z-index

Needs a value between fixed and the offcanvas backdrop, see https://getbootstrap.com/docs/5.1/layout/z-index/

* Update receive and send views
This commit is contained in:
d11n 2022-02-17 10:07:41 +01:00 committed by GitHub
parent cd3807a3d8
commit 5c8ca15ee2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 400 additions and 296 deletions

View file

@ -403,9 +403,9 @@ namespace BTCPayServer.Tests
public void GoToWalletSettings(string cryptoCode = "BTC")
{
Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
if (Driver.PageSource.Contains("id=\"SectionNav-Settings\""))
if (Driver.PageSource.Contains("id=\"WalletNav-Settings\""))
{
Driver.FindElement(By.Id("SectionNav-Settings")).Click();
Driver.FindElement(By.Id("WalletNav-Settings")).Click();
}
}
@ -565,7 +565,7 @@ namespace BTCPayServer.Tests
Driver.Navigate().GoToUrl(new Uri(ServerUri, $"wallets/{walletId}"));
if (navPages != WalletsNavPages.Transactions)
{
Driver.FindElement(By.Id($"SectionNav-{navPages}")).Click();
Driver.FindElement(By.Id($"WalletNav-{navPages}")).Click();
}
}

View file

@ -998,13 +998,13 @@ namespace BTCPayServer.Tests
//let's test quickly the receive wallet page
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
s.Driver.FindElement(By.Id("SectionNav-Send")).Click();
s.Driver.FindElement(By.Id("WalletNav-Send")).Click();
s.Driver.FindElement(By.Id("SignTransaction")).Click();
//you cannot use the Sign with NBX option without saving private keys when generating the wallet.
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
s.Driver.FindElement(By.Id("SectionNav-Receive")).Click();
s.Driver.FindElement(By.Id("WalletNav-Receive")).Click();
//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);
@ -1071,20 +1071,19 @@ namespace BTCPayServer.Tests
Assert.Contains("m/84'/1'/0'",
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).GetAttribute("value"));
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
// Make sure we can rescan, because we are admin!
s.Driver.FindElement(By.Id("SectionNav-Rescan")).Click();
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
s.Driver.FindElement(By.Id("Rescan")).Click();
Assert.Contains("The batch size make sure", s.Driver.PageSource);
// Check the tx sent earlier arrived
s.Driver.FindElement(By.Id("SectionNav-Transactions")).Click();
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
var walletTransactionLink = s.Driver.Url;
Assert.Contains(tx.ToString(), s.Driver.PageSource);
// Send to bob
s.Driver.FindElement(By.Id("SectionNav-Send")).Click();
s.Driver.FindElement(By.Id("WalletNav-Send")).Click();
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, bob, 1);
s.Driver.FindElement(By.Id("SignTransaction")).Click();
@ -1096,7 +1095,7 @@ namespace BTCPayServer.Tests
Assert.Equal(walletTransactionLink, s.Driver.Url);
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
s.Driver.FindElement(By.Id("SectionNav-Send")).Click();
s.Driver.FindElement(By.Id("WalletNav-Send")).Click();
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, jack, 0.01m);
@ -1113,7 +1112,7 @@ namespace BTCPayServer.Tests
bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
s.Driver.FindElement(By.Id("SectionNav-Send")).Click();
s.Driver.FindElement(By.Id("WalletNav-Send")).Click();
s.Driver.FindElement(By.Id("bip21parse")).Click();
s.Driver.SwitchTo().Alert().SendKeys(bip21);
s.Driver.SwitchTo().Alert().Accept();

View file

@ -19,7 +19,6 @@ namespace BTCPayServer.Components.MainNav
{
public class MainNav : ViewComponent
{
private const string RootName = "Global";
private readonly AppService _appService;
private readonly StoreRepository _storeRepo;
private readonly UIStoresController _storesController;

View file

@ -0,0 +1,29 @@
@using BTCPayServer.Views.Stores
@using BTCPayServer.Client
@using BTCPayServer.Views.Wallets
@using BTCPayServer.Abstractions.Extensions
@inject BTCPayNetworkProvider _btcPayNetworkProvider
@model BTCPayServer.Components.WalletNav.WalletNavViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
<div class="d-sm-flex align-items-center justify-content-between">
<a asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@Model.WalletId" class="unobtrusive-link">
<h2 class="mb-1">@Model.Label</h2>
<div class="text-muted fw-semibold">
@Model.Balance @Model.Network.CryptoCode
</div>
</a>
<div class="d-flex gap-3 mt-3 mt-sm-0" permission="@Policies.CanModifyStoreSettings">
@if (!Model.Network.ReadonlyWallet)
{
<a class="btn btn-primary" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@Model.WalletId" id="WalletNav-Send">Send</a>
}
<a class="btn btn-primary" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@Model.WalletId" id="WalletNav-Receive">Receive</a>
<a class="btn btn-secondary @ViewData.IsActivePage(WalletsNavPages.Settings) @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@Model.WalletId.CryptoCode" asp-route-storeId="@Model.WalletId.StoreId" title="Settings" id="WalletNav-Settings">
<vc:icon symbol="settings"/>
</a>
</div>
</div>
<vc:ui-extension-point location="wallet-nav" model="@Model"/>

View file

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using NBitcoin;
using NBitcoin.Secp256k1;
namespace BTCPayServer.Components.WalletNav
{
public class WalletNav : ViewComponent
{
private readonly BTCPayWalletProvider _walletProvider;
private readonly UIWalletsController _walletsController;
private readonly BTCPayNetworkProvider _networkProvider;
public WalletNav(
BTCPayWalletProvider walletProvider,
BTCPayNetworkProvider networkProvider,
UIWalletsController walletsController)
{
_walletProvider = walletProvider;
_networkProvider = networkProvider;
_walletsController = walletsController;
}
public async Task<IViewComponentResult> InvokeAsync(WalletId walletId)
{
var store = ViewContext.HttpContext.GetStoreData();
var network = _networkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
var wallet = _walletProvider.GetWallet(network);
var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode);
var balance = await _walletsController.GetBalanceString(wallet, derivation.AccountDerivation);
var vm = new WalletNavViewModel
{
WalletId = walletId,
Network = network,
Balance = balance,
Label = derivation.Label ?? $"{store.StoreName} {walletId.CryptoCode} Wallet"
};
return View(vm);
}
}
}

View file

@ -0,0 +1,10 @@
namespace BTCPayServer.Components.WalletNav
{
public class WalletNavViewModel
{
public WalletId WalletId { get; set; }
public BTCPayNetwork Network { get; set; }
public string Label { get; set; }
public string Balance { get; set; }
}
}

View file

@ -1057,7 +1057,7 @@ namespace BTCPayServer.Controllers
}
}
private static async Task<string> GetBalanceString(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy)
internal async Task<string> GetBalanceString(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy)
{
try
{

View file

@ -10,6 +10,7 @@ namespace BTCPayServer.Services.Labels
public string Text { get; internal set; }
public string Color { get; internal set; }
public string TextColor { get; internal set; }
public string Link { get; internal set; }
public string Tooltip { get; internal set; }

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using Amazon.Util.Internal.PlatformServices;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
@ -41,12 +42,13 @@ namespace BTCPayServer.Services.Labels
private ColoredLabel CreateLabel(LabelData uncoloredLabel, string color, HttpRequest request)
{
ArgumentNullException.ThrowIfNull(uncoloredLabel);
color = color ?? DefaultColor;
color ??= DefaultColor;
ColoredLabel coloredLabel = new ColoredLabel()
ColoredLabel coloredLabel = new ColoredLabel
{
Text = uncoloredLabel.Text,
Color = color
Color = color,
TextColor = TextColor(color)
};
if (uncoloredLabel is ReferenceLabel refLabel)
{
@ -90,5 +92,14 @@ namespace BTCPayServer.Services.Labels
}
return coloredLabel;
}
private string TextColor(string bgColor)
{
int nThreshold = 105;
var bg = ColorTranslator.FromHtml(bgColor);
int bgDelta = Convert.ToInt32((bg.R * 0.299) + (bg.G * 0.587) + (bg.B * 0.114));
Color color = (255 - bgDelta < nThreshold) ? Color.Black : Color.White;
return ColorTranslator.ToHtml(color);
}
}
}

View file

@ -66,7 +66,7 @@
}
/* pull mass action form up, so that it is besides the search form */
@@media (min-width: 992px) {
@@media (min-width: 1200px) {
#MassAction {
margin-top: -4rem;
}
@ -267,7 +267,7 @@
</ul>
</div>
</div>
<form class="col-lg-6 col-xl-8 mb-4" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" method="get">
<form class="@(Model.Total > 0 ? "col-xl-7 col-xxl-8 " : "")mb-4" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" method="get">
<input type="hidden" asp-for="Count" />
<input asp-for="TimezoneOffset" type="hidden" />
<div class="input-group">
@ -302,38 +302,38 @@
<span asp-validation-for="SearchTerm" class="text-danger"></span>
</form>
<form method="post" id="MassAction" asp-action="MassAction" class="">
<div class="d-inline-flex align-items-center pb-2 float-lg-end mb-2">
<input type="hidden" name="storeId" value="@Model.StoreId" />
<a href="https://docs.btcpayserver.org/Accounting/" class="ms-2 ms-lg-0 me-lg-2 order-1 order-lg-0" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
<span class="me-2">
<button class="btn btn-secondary dropdown-toggle mb-1" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
<button type="submit" class="dropdown-item" name="command" value="archive" id="ActionsDropdownArchive"><i class="fa fa-archive"></i> Archive</button>
@if (Model.IncludeArchived)
{
<button type="submit" asp-action="MassAction" class="dropdown-item" name="command" value="unarchive" id="ActionsDropdownUnarchive"><i class="fa fa-archive"></i> Unarchive</button>
}
<button id="BumpFee" type="submit" permission="@Policies.CanModifyStoreSettings" class="dropdown-item" name="command" value="cpfp">Bump fee</button>
</div>
</span>
<span>
<a class="btn btn-secondary dropdown-toggle mb-1" href="#" role="button" id="ExportDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Export
</a>
<div class="dropdown-menu" aria-labelledby="ExportDropdownToggle">
<a asp-action="Export" asp-route-timezoneoffset="0" asp-route-format="csv" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item export-link" target="_blank">CSV</a>
<a asp-action="Export" asp-route-timezoneoffset="0" asp-route-format="json" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item export-link" target="_blank">JSON</a>
</div>
</span>
</div>
<div style="clear:both"></div>
@if (Model.Total > 0)
{
@if (Model.Total > 0)
{
<form method="post" id="MassAction" asp-action="MassAction" class="">
<div class="d-inline-flex align-items-center pb-2 float-xl-end mb-2 gap-3">
<input type="hidden" name="storeId" value="@Model.StoreId" />
<span class="order-xl-1">
<button class="btn btn-secondary dropdown-toggle mb-1" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu dropdown-menu-xl-end" aria-labelledby="ActionsDropdownToggle">
<button type="submit" class="dropdown-item" name="command" value="archive" id="ActionsDropdownArchive">Archive</button>
@if (Model.IncludeArchived)
{
<button type="submit" asp-action="MassAction" class="dropdown-item" name="command" value="unarchive" id="ActionsDropdownUnarchive">Unarchive</button>
}
<button id="BumpFee" type="submit" permission="@Policies.CanModifyStoreSettings" class="dropdown-item" name="command" value="cpfp">Bump fee</button>
</div>
</span>
<span class="d-inline-flex align-items-center gap-3">
<a class="btn btn-secondary dropdown-toggle mb-1 order-xl-1" href="#" role="button" id="ExportDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Export
</a>
<div class="dropdown-menu" aria-labelledby="ExportDropdownToggle">
<a asp-action="Export" asp-route-timezoneoffset="0" asp-route-format="csv" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item export-link" target="_blank">CSV</a>
<a asp-action="Export" asp-route-timezoneoffset="0" asp-route-format="json" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item export-link" target="_blank">JSON</a>
</div>
<a href="https://docs.btcpayserver.org/Accounting/" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</span>
</div>
<div style="clear:both"></div>
<div class="table-responsive">
<table id="invoices" class="table table-hover">
<thead>
@ -452,11 +452,12 @@
</div>
<vc:pager view-model="Model" />
}
else
{
<p class="text-secondary mt-3">
There are no invoices matching your criteria.
</p>
}
</form>
</form>
}
else
{
<p class="text-secondary mt-3">
There are no invoices matching your criteria.
</p>
}

View file

@ -19,48 +19,56 @@
<h3 class="mb-3">@ViewData["Title"]</h3>
<div class="mb-3 d-flex align-items-center">
<span title="@Model.Source" data-bs-toggle="tooltip" class="me-3">@(Model.IsHotWallet ? "Hot wallet" : "Watch-only wallet")</span>
<form method="get" asp-action="DeleteWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="d-inline">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle mb-1" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
@if (Model.NBXSeedAvailable)
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
<a class="dropdown-item" asp-controller="UIWallets" asp-action="WalletRescan" asp-route-walletId="@Model.WalletId" id="Rescan">Rescan wallet for missing transactions</a>
<form method="post" asp-controller="UIWallets" asp-action="WalletActions" asp-route-walletId="@Model.WalletId">
<button name="command" type="submit" class="dropdown-item" value="prune">Prune old transactions from history</button>
@if (User.IsInRole(Roles.ServerAdmin))
{
<a asp-action="WalletSeed" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="dropdown-item" id="ViewSeed">View seed</a>
<button name="command" type="submit" class="dropdown-item" value="clear">Clear all transactions from history</button>
}
@if (Model.UriScheme == "bitcoin")
{
<button type="button" class="dropdown-item" id="RegisterWallet" data-store="@Model.StoreName" data-scheme="@Model.UriScheme" data-url="@Url.Action("WalletSend", "UIWallets", new {walletId = Model.WalletId, bip21 = "%s"})" hidden>Register wallet for payment links</button>
}
<a asp-controller="UIStores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
id="ChangeWalletLink"
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#ConfirmModal"
data-title="Replace @Model.CryptoCode wallet"
data-description="@ViewData["ReplaceDescription"]"
data-confirm="Setup new wallet"
data-confirm-input="REPLACE">
Replace wallet
</a>
</form>
@if (Model.UriScheme == "bitcoin")
{
<button type="button" class="dropdown-item" id="RegisterWallet" data-store="@Model.StoreName" data-scheme="@Model.UriScheme" data-url="@Url.Action("WalletSend", "UIWallets", new {walletId = Model.WalletId, bip21 = "%s"})" hidden>Register wallet for payment links</button>
}
<div class="dropdown-divider"></div>
@if (Model.NBXSeedAvailable)
{
<a asp-action="WalletSeed" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="dropdown-item" id="ViewSeed">View seed</a>
}
<a asp-controller="UIStores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
id="ChangeWalletLink"
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#ConfirmModal"
data-title="Replace @Model.CryptoCode wallet"
data-description="@ViewData["ReplaceDescription"]"
data-confirm="Setup new wallet"
data-confirm-input="REPLACE">
Replace wallet
</a>
<form method="get" asp-action="DeleteWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="d-inline">
<button type="submit"
id="Delete"
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#ConfirmModal"
data-title="Remove @Model.CryptoCode wallet"
data-description="@ViewData["RemoveDescription"]"
data-confirm="Remove"
data-confirm-input="REMOVE">Remove wallet</button>
</div>
id="Delete"
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#ConfirmModal"
data-title="Remove @Model.CryptoCode wallet"
data-description="@ViewData["RemoveDescription"]"
data-confirm="Remove"
data-confirm-input="REMOVE">Remove wallet</button>
</form>
</div>
</form>
</div>
</div>
<form method="post" asp-action="UpdateWalletSettings" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode">
<div class="form-group">
<div class="form-group my-4">
<div class="d-flex align-items-center">
<input asp-for="Enabled" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="Enabled" class="form-label mb-0 me-1"></label>

View file

@ -13,18 +13,17 @@
<div class="row no-gutters">
<div class="col-xl-8 col-xxl-constrain">
<form method="post" asp-action="WalletReceive" class="text-center">
<form method="post" asp-action="WalletReceive">
@if (string.IsNullOrEmpty(Model.Address))
{
<h3 class="mb-3">@ViewData["Title"]</h3>
<button id="generateButton" class="btn btn-lg btn-primary" type="submit" name="command" value="generate-new-address">Generate @Model.CryptoCode address</button>
<button id="generateButton" class="btn btn-primary" type="submit" name="command" value="generate-new-address">Generate next available @Model.CryptoCode address</button>
}
else
{
<h3 class="mb-4">@Model.CryptoCode&nbsp;Address</h3>
<noscript>
<div class="m-sm-0 p-sm-0">
<div class="form-group">
<div class="form-group">
<input type="text" class="form-control " readonly="readonly" asp-for="Address" id="address"/>
</div>
<div class="form-group">
@ -51,7 +50,7 @@
<vc:qr-code data="@Model.Address"/>
</div>
</div>
<div class="nav justify-content-center">
<div class="nav">
<a class="btcpay-pill active" data-bs-toggle="tab" href="#link-tab">Link</a>
<a class="btcpay-pill" data-bs-toggle="tab" href="#address-tab">Address</a>
</div>
@ -59,13 +58,17 @@
<div class="form-group">
<div class="input-group" data-clipboard="@Model.Address">
<input type="text" class="form-control" style="cursor:copy" readonly="readonly" value="@Model.Address" id="address"/>
<button type="button" class="input-group-text btn btn-outline-secondary" style="width:10em;" data-clipboard-confirm>Copy address</button>
<button type="button" class="input-group-text btn btn-outline-secondary p-2" style="width:7em;" data-clipboard-confirm>
<vc:icon symbol="copy"/>
</button>
</div>
</div>
<div class="form-group">
<div class="input-group" data-clipboard="@Model.PaymentLink">
<input type="text" class="form-control" style="cursor:copy" readonly="readonly" value="@Model.PaymentLink" id="payment-link"/>
<button type="button" class="btn btn-outline-secondary" style="width:10em;" data-clipboard-confirm>Copy link</button>
<button type="button" class="btn btn-outline-secondary p-2" style="width:7em;" data-clipboard-confirm>
<vc:icon symbol="copy"/>
</button>
</div>
</div>
<div class="row mt-4">

View file

@ -30,7 +30,6 @@
<div class="row">
<div class="col-xl-8 col-xxl-constrain @(!Model.InputSelection && Model.Outputs.Count == 1 ? "transaction-output-form" : "")">
<h3 class="mb-3">@ViewData["Title"]</h3>
<form method="post" asp-action="WalletSend" asp-route-walletId="@walletId">
<input type="hidden" asp-for="InputSelection" />
<input type="hidden" asp-for="FiatDivisibility" />
@ -229,10 +228,11 @@
</div>
</div>
</div>
<div class="form-group d-flex mt-2">
<div class="form-group d-flex gap-3 mt-2">
<button type="submit" id="SignTransaction" name="command" value="sign" class="btn btn-primary">Sign transaction</button>
<button type="button" id="bip21parse" class="ms-3 btn btn-secondary" title="Paste BIP21/Address"><i class="fa fa-paste"></i></button>
<button type="button" id="scanqrcode" class="ms-3 btn btn-secondary only-for-js" data-bs-toggle="modal" data-bs-target="#scanModal" title="Scan BIP21/Address with camera"><i class="fa fa-camera"></i></button>
<a class="btn btn-secondary" asp-controller="UIWallets" asp-action="WalletPSBT" asp-route-walletId="@walletId" id="PSBT">PSBT</a>
<button type="button" id="bip21parse" class="btn btn-secondary" title="Paste BIP21/Address"><i class="fa fa-paste"></i></button>
<button type="button" id="scanqrcode" class="btn btn-secondary only-for-js" data-bs-toggle="modal" data-bs-target="#scanModal" title="Scan BIP21/Address with camera"><i class="fa fa-camera"></i></button>
</div>
</form>
</div>

View file

@ -13,7 +13,7 @@
@@media (min-width: 990px) {
.smMaxWidth {
max-width: 250px;
max-width: 200px;
}
}
@ -76,187 +76,174 @@
</script>
}
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">@ViewData["Title"]</h3>
<form id="WalletActions" method="post" asp-action="WalletActions" asp-route-walletId="@Context.GetRouteValue("walletId")">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle mb-1" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
<button id="BumpFee" name="command" type="submit" class="dropdown-item" value="cpfp">Bump fee (CPFP)</button>
<a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")" class="dropdown-item">Rescan wallet for missing transactions</a>
<button name="command" type="submit" class="dropdown-item" value="prune">Prune old transactions from history</button>
@if (User.IsInRole(Roles.ServerAdmin))
{
<button name="command" type="submit" class="dropdown-item" value="clear">Clear all transactions from history</button>
}
</div>
</div>
</form>
</div>
<p class="mb-0">
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")">rescan your wallet</a>.
<br/>
If some transactions appear in BTCPay Server, but are missing in another wallet, <a href="https://docs.btcpayserver.org/FAQ/Wallet/#missing-payments-in-my-software-or-hardware-wallet" rel="noreferrer noopener">follow these instructions</a>.
</p>
@if (Model.Transactions.Any())
{
@if (Model.Labels.Any())
{
<div class="row mt-4">
<div class="col-md-12">
<div class="d-flex flex-row align-items-center flex-wrap card card-body px-3 py-2">
<span class="me-2">Filter by label:</span>
<div class="d-flex align-items-center gap-3 mb-2">
@if (Model.Labels.Any())
{
<div class="input-group">
<span class="input-group-text">Filter</span>
<div class="form-control d-flex flex-wrap gap-2 align-items-center">
@foreach (var label in Model.Labels)
{
<a asp-route-labelFilter="@label.Text" class="badge me-2 my-1 position-relative text-white d-block" style="background-color: @label.Color;">@label.Text</a>
<a asp-route-labelFilter="@label.Text" class="badge position-relative text-white flex-grow-0" style="background-color:@label.Color;color:@label.TextColor !important;">@label.Text</a>
}
</div>
</div>
}
<div class="dropdown ms-auto">
<button class="btn btn-secondary dropdown-toggle" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="ActionsDropdownToggle">
<form id="WalletActions" method="post" asp-action="WalletActions" asp-route-walletId="@walletId">
<button id="BumpFee" name="command" type="submit" class="dropdown-item" value="cpfp">Bump fee (CPFP)</button>
</form>
</div>
</div>
}
<div class="table-responsive-md">
<table class="table table-hover">
<thead class="thead-inverse">
<tr>
<th style="width:2rem;" class="only-for-js">
<input id="selectAllCheckbox" type="checkbox" class="form-check-input" />
</th>
<th style="min-width: 90px;" class="col-md-auto">
Date
<a id="switchTimeFormat" href="#">
<span class="fa fa-clock-o" title="Switch date format"></span>
</a>
</th>
<th class="text-start">Label</th>
<th>Transaction Id</th>
<th class="text-end">Amount</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
@foreach (var transaction in Model.Transactions)
{
</div>
<div class="row">
<div class="col table-responsive-md">
<table class="table table-hover">
<thead class="thead-inverse">
<tr>
<td class="only-for-js">
<input name="selectedTransactions" type="checkbox" class="selector form-check-input" form="WalletActions" value="@transaction.Id" />
</td>
<td>
<span class="switchTimeFormat" data-switch="@transaction.Timestamp.ToTimeAgo()">
@transaction.Timestamp.ToBrowserDate()
</span>
</td>
<td class="text-start">
@foreach (var label in transaction.Labels)
{
<div class="badge-container">
<div
class="badge transactionLabel position-relative text-white d-block"
style="background-color: @label.Color; padding-right: 16px; z-index: 1;"
data-bs-toggle="tooltip"
title="@label.Tooltip">
<a asp-route-labelFilter="@label.Text" class="text-white">@label.Text</a>
<th style="width:2rem;" class="only-for-js">
<input id="selectAllCheckbox" type="checkbox" class="form-check-input"/>
</th>
<th style="min-width: 90px;" class="col-md-auto">
Date
<a id="switchTimeFormat" href="#">
<span class="fa fa-clock-o" title="Switch date format"></span>
</a>
</th>
<th class="text-start">Label</th>
<th>Transaction Id</th>
<th class="text-end">Amount</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody>
@foreach (var transaction in Model.Transactions)
{
<tr>
<td class="only-for-js">
<input name="selectedTransactions" type="checkbox" class="selector form-check-input" form="WalletActions" value="@transaction.Id"/>
</td>
<td>
<span class="switchTimeFormat" data-switch="@transaction.Timestamp.ToTimeAgo()">
@transaction.Timestamp.ToBrowserDate()
</span>
</td>
<td class="text-start">
@foreach (var label in transaction.Labels)
{
<div class="badge-container">
<div
class="badge transactionLabel position-relative text-white d-block"
style="background-color:@label.Color;padding-right: 16px; z-index: 1;"
data-bs-toggle="tooltip"
title="@label.Tooltip">
<a asp-route-labelFilter="@label.Text" style="color:@label.TextColor !important;">@label.Text</a>
<form
asp-route-walletId="@Context.GetRouteValue("walletId")"
asp-action="ModifyTransaction"
method="post"
class="removeTransactionLabelForm">
<form
asp-route-walletId="@Context.GetRouteValue("walletId")"
asp-action="ModifyTransaction"
method="post"
class="removeTransactionLabelForm">
<input type="hidden" name="transactionId" value="@transaction.Id"/>
<button
name="removelabel"
style="color: @label.Color; filter: brightness(0.5);"
type="submit"
value="@label.Text">
<span class="fa fa-close"></span>
</button>
</form>
</div>
@if (!string.IsNullOrEmpty(label.Link))
{
<a href="@label.Link" target="_blank" class="badge transaction-details-icon" style="background-color: @label.Color; filter: brightness(1.1);" rel="noreferrer noopener">
<span class="fa fa-info-circle" title="Transaction details" style="color: @label.Color; filter: brightness(0.5);">
<span class="visually-hidden">Transaction details</span>
</span>
</a>
}
</div>
}
</td>
<td class="smMaxWidth text-truncate @(transaction.IsConfirmed ? "" : "unconf")">
<a href="@transaction.Link" target="_blank" rel="noreferrer noopener">
@transaction.Id
</a>
</td>
@if (transaction.Positive)
{
<td class="text-end text-success">@transaction.Balance</td>
}
else
{
<td class="text-end text-danger">@transaction.Balance</td>
}
<td class="text-end">
<div class="dropstart d-inline-block">
<span class="fa fa-tags cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu">
<form asp-action="ModifyTransaction" method="post"
asp-route-walletId="@Context.GetRouteValue("walletId")">
<input type="hidden" name="transactionId" value="@transaction.Id"/>
<button
name="removelabel"
style="color: @label.Color; filter: brightness(0.5);"
type="submit"
value="@label.Text">
<span class="fa fa-close"></span>
</button>
<div class="input-group input-group-sm p-2">
<input name="addlabel" placeholder="Label name" maxlength="20" type="text" class="form-control form-control-sm"/>
<button type="submit" class="btn btn-primary btn-sm"><span class="fa fa-plus"></span></button>
</div>
@if (Model.Labels.Count > 0)
{
<div class="dropdown-divider"></div>
<div class="px-2">
@foreach (var label in Model.Labels)
{
@if (transaction.Labels.Contains(label))
{
<button name="removelabel" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="display:block;background-color:@label.Color;color:white;text-align:left;"><span class="fa fa-check"></span> @label.Text</span></button>
}
else
{
<button name="addlabelclick" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="display:block;background-color:@label.Color;color:white;text-align:left;">@label.Text</span></button>
}
}
</div>
}
</form>
</div>
@if (!string.IsNullOrEmpty(label.Link))
</div>
<div class="dropstart d-inline-block">
@if (string.IsNullOrEmpty(transaction.Comment))
{
<a href="@label.Link" target="_blank" class="badge transaction-details-icon" style="background-color: @label.Color; filter: brightness(1.1);" rel="noreferrer noopener">
<span class="fa fa-info-circle" title="Transaction details" style="color: @label.Color; filter: brightness(0.5);">
<span class="visually-hidden">Transaction details</span>
</span>
</a>
<span class="fa fa-comment cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
}
</div>
}
</td>
<td class="smMaxWidth text-truncate @(transaction.IsConfirmed ? "" : "unconf")">
<a href="@transaction.Link" target="_blank" rel="noreferrer noopener">
@transaction.Id
</a>
</td>
@if (transaction.Positive)
{
<td class="text-end text-success">@transaction.Balance</td>
}
else
{
<td class="text-end text-danger">@transaction.Balance</td>
}
<td class="text-end">
<div class="dropstart d-inline-block">
<span class="fa fa-tags cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu">
<form asp-action="ModifyTransaction" method="post"
asp-route-walletId="@Context.GetRouteValue("walletId")">
<input type="hidden" name="transactionId" value="@transaction.Id"/>
<div class="input-group input-group-sm p-2">
<input name="addlabel" placeholder="Label name" maxlength="20" type="text" class="form-control form-control-sm"/>
<button type="submit" class="btn btn-primary btn-sm"><span class="fa fa-plus"></span></button>
</div>
@if (Model.Labels.Count > 0)
{
<div class="dropdown-divider"></div>
<div class="px-2">
@foreach (var label in Model.Labels)
{
@if (transaction.Labels.Contains(label))
{
<button name="removelabel" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="display:block;background-color:@label.Color;color:white;text-align:left;"><span class="fa fa-check"></span> @label.Text</span></button>
}
else
{
<button name="addlabelclick" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="display:block;background-color:@label.Color;color:white;text-align:left;">@label.Text</span></button>
}
}
else
{
<span class="fa fa-commenting cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
}
<div class="dropdown-menu">
<form asp-action="ModifyTransaction" method="post"
asp-route-walletId="@Context.GetRouteValue("walletId")">
<input type="hidden" name="transactionId" value="@transaction.Id"/>
<div class="input-group p-2">
<textarea name="addcomment" maxlength="200" rows="2" cols="20" class="form-control form-control-sm p-1">@transaction.Comment</textarea>
</div>
}
</form>
<div class="p-2">
<button type="submit" class="btn btn-primary btn-sm">Save comment</button>
</div>
</form>
</div>
</div>
</div>
<div class="dropstart d-inline-block">
@if (string.IsNullOrEmpty(transaction.Comment))
{
<span class="fa fa-comment cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
}
else
{
<span class="fa fa-commenting cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
}
<div class="dropdown-menu">
<form asp-action="ModifyTransaction" method="post"
asp-route-walletId="@Context.GetRouteValue("walletId")">
<input type="hidden" name="transactionId" value="@transaction.Id"/>
<div class="input-group p-2">
<textarea name="addcomment" maxlength="200" rows="2" cols="20" class="form-control form-control-sm p-1">@transaction.Comment</textarea>
</div>
<div class="p-2">
<button type="submit" class="btn btn-primary btn-sm">Save comment</button>
</div>
</form>
</div>
</div>
</td>
</tr>
}
</tbody>
</table>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<vc:pager view-model="Model"/>
}
@ -266,3 +253,9 @@ else
There are no transactions yet.
</p>
}
<p class="mt-4 mb-0">
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")">rescan your wallet</a>.
<br/>
If some transactions appear in BTCPay Server, but are missing in another wallet, <a href="https://docs.btcpayserver.org/FAQ/Wallet/#missing-payments-in-my-software-or-hardware-wallet" rel="noreferrer noopener">follow these instructions</a>.
</p>

View file

@ -1,27 +1,15 @@
@using BTCPayServer.Views.Stores
@using BTCPayServer.Client
@inject BTCPayNetworkProvider _btcPayNetworkProvider
@{
var walletId = Context.GetRouteValue("walletId")?.ToString();
var storeId = Context.GetRouteValue("storeId")?.ToString();
var cryptoCode = Context.GetRouteValue("cryptoCode")?.ToString();
var wallet = walletId != null ? WalletId.Parse(walletId) : new WalletId(storeId, cryptoCode);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(wallet.CryptoCode);
}
<nav id="SectionNav">
<div class="nav">
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Transactions)" asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@wallet" id="SectionNav-Transactions" permission="@Policies.CanModifyStoreSettings">Transactions</a>
@if (!network.ReadonlyWallet)
{
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Send)" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@wallet" id="SectionNav-Send" permission="@Policies.CanModifyStoreSettings">Send</a>
}
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Receive)" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@wallet" id="SectionNav-Receive" permission="@Policies.CanModifyStoreSettings">Receive</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-controller="UIWallets" asp-action="WalletRescan" asp-route-walletId="@wallet" id="SectionNav-Rescan" permission="@Policies.CanModifyServerSettings">Rescan</a>
@if (!network.ReadonlyWallet)
{
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-controller="UIWallets" asp-action="WalletPSBT" asp-route-walletId="@wallet" id="SectionNav-PSBT" permission="@Policies.CanModifyStoreSettings">PSBT</a>
}
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Settings) @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@wallet.CryptoCode" asp-route-storeId="@wallet.StoreId" id="SectionNav-Settings" permission="@Policies.CanModifyStoreSettings">Settings</a>
<vc:ui-extension-point location="wallet-nav" model="@Model"/>
</div>
</nav>
<div class="sticky-header-setup"></div>
<header class="sticky-header">
<vc:wallet-nav wallet-id="wallet"/>
</header>
<script>
const { offsetHeight } = document.querySelector('.sticky-header-setup + .sticky-header');
document.documentElement.style.scrollPaddingTop = `calc(${offsetHeight}px + var(--btcpay-space-m))`;
</script>

View file

@ -3,6 +3,7 @@
<symbol id="onion" viewBox="0 0 61 91"><g fill="currentColor"><path d="m34.9 6.8-2.4 9.6C35.9 9.6 41.4 4.5 47.7 0c-4.6 5.3-8.8 10.6-11.3 16C40.7 9.9 46.5 6.6 53 4.3 44.3 12 37.4 20.4 32.2 28.7L28 26.9c.7-6.7 3.2-13.5 6.9-20.1z"/><path d="m31.7 28.3 2.9 1.5c-.3 1.9.1 6.1 2 7.1 8.4 5.2 16.2 10.8 19.3 16.5 11 19.9-7.7 38.4-24 36.6 8.8-6.5 11.4-19.9 8.1-34.6-1.3-5.7-3.4-10.9-7.1-16.8-1.6-2.7-1-6.3-1.2-10.3zM28.5 37.8c-.6 3.1-1.3 8.7-4 10.8-1.1.8-2.3 1.6-3.5 2.4-4.9 3.3-9.7 6.4-11.9 14.3-.5 1.7-.1 3.5.3 5.2 1.2 4.9 4.6 10.1 7.3 13.2 0 .1.5.5.5.6 2.2 2.6 2.9 3.4 11.3 5.3l-.2.9c-5.1-1.3-9.2-2.6-11.9-5.6 0-.1-.5-.5-.5-.5-2.8-3.2-6.3-8.6-7.5-13.7-.5-2-.9-3.6-.3-5.7 2.3-8.2 7.3-11.5 12.3-14.9 1.1-.7 2.5-1.4 3.6-2.3 2.3-1.5 3.4-6.2 4.5-10z"/><path d="M30.7 50.8c.1 3.5-.3 5.3.6 7.8.5 1.5 2.4 3.5 2.9 5.5.7 2.6 1.5 5.5 1.5 7.3 0 2-.1 5.8-1 9.8-.7 3.3-2.2 6.2-4.8 7.8-2.7-.5-5.8-1.5-7.6-3.1-3.6-3.1-6.7-8.3-7.1-12.8-.3-3.7 3.1-9.2 7.9-11.9 4-2.4 5-5 5.9-9.4-1.2 3.8-2.4 6.9-6.3 9-5.7 3-8.6 7.9-8.3 12.7.4 6.1 2.8 10.2 7.6 13.5 2 1.4 5.8 2.9 8.2 3.3V90c1.8-.3 4.1-3.3 5.3-7.2 1-3.6 1.4-8.1 1.3-11-.1-1.7-.8-5.3-2.2-8.6-.7-1.8-1.9-3.6-2.6-4.9-.9-1.5-.9-4.3-1.3-7.5z"/><path d="M30.1 64.5c.1 2.4 1 5.4 1.4 8.5.3 2.3.2 4.6.1 6.6-.1 2.3-.8 6.5-1.9 8.6-1-.5-1.4-1-2.1-1.8-.8-1.1-1.4-2.3-1.9-3.6-.4-1-.9-2.2-1.1-3.5-.3-2-.2-5.2 2.1-8.4 1.8-2.6 2.2-2.8 2.8-5.7-.8 2.6-1.4 2.9-3.3 5.1-2.1 2.4-2.4 6-2.4 8.9 0 1.2.5 2.6 1 3.8.5 1.3 1 2.7 1.7 3.7 1.1 1.6 2.5 2.6 3.2 2.7v-.1c1.3-1.5 2.1-2.9 2.4-4.4.3-1.8.4-3.5.6-5.6.2-1.8.1-4.1-.4-6.5-.6-3-1.7-6.1-2.2-8.3z"/><path d="M30.5 35c.1 3.5.3 10 1.3 12.6.3.9 2.8 4.7 4.5 9.4 1.2 3.2 1.5 6.2 1.7 7.1.8 3.8-.2 10.3-1.5 16.4-.7 3.3-3 7.4-5.6 9l-.5.9c1.5-.1 5.1-3.6 6.4-8.1 2.2-7.5 3-11 2-19.4-.1-.8-.5-3.6-1.8-6.5-1.9-4.5-4.6-8.8-4.9-9.7-.7-1.4-1.5-7.6-1.6-11.7z"/><path d="M31.7 28.6c-.2 3.6-.2 6.4.4 9.1.7 2.9 4.5 7.1 6.1 11.9 3 9.2 2.2 21.2.1 30.5-.8 3.3-4.6 8.1-8.5 9.6l2.8.7c1.5-.1 5.5-3.8 7.1-8 2.5-6.7 3-14.6 2-23-.1-.8-1.4-8-2.7-11-1.8-4.5-4.7-7.7-5.7-10.5-.8-2.1-1.1-7.7-.6-8.8l-1-.5z"/><path d="M51.7 46.3c-2.9-2.6-6.5-4.8-10.3-6.9-1.7-.9-6.9-5-5.1-10.8l-13.1-5.4-.9.7c4.4 7.9 2.1 12.1-.1 13.5-4.4 3-10.8 6.8-13.9 10.1C2.2 53.8.4 59.8 1 67.6c.6 10.1 7.9 18.5 17.8 21.8 4.3 1.4 8.3 1.6 12.7 1.6 7.1 0 14.5-1.9 19.8-6.3 5.7-4.7 9-11.8 9-19.1 0-7.3-3.1-14.3-8.6-19.3zm-1.9 36.9c-4.9 4-13.7 6.8-18.4 6.6-5.2-.3-10.3-1.1-14.8-3.3C8.7 82.7 3.5 74.4 3.1 67.7 2.4 54 9 50.1 15.1 45.1c3.4-2.8 8.2-4.2 10.9-9.2.5-1.1.8-3.5.2-6-.3-.9-1.5-3.9-2-4.6l9.8 4.3c-1.2 4.5 2.5 9.2 5.5 10.9 3 1.7 7.7 4.9 10.6 7.5 5.1 4.5 7.7 10.9 7.7 17.6 0 6.7-2.8 13.3-8 17.6z"/></g></symbol>
<symbol id="back" viewBox="0 0 21 18"><path d="M7.63754 1.10861L0.578503 8.16764C0.119666 8.62648 0.119666 9.37121 0.578503 9.83122L7.63754 16.8902C8.09637 17.3491 8.8411 17.3491 9.30111 16.8902C9.53053 16.6608 9.64583 16.3608 9.64583 16.0585C9.64583 15.7561 9.53053 15.4561 9.30111 15.2267L4.25038 10.1759H19.0579C19.7085 10.1759 20.2344 9.65004 20.2344 8.99943C20.2344 8.34882 19.7085 7.82293 19.0579 7.82293L4.25038 7.82293L9.30111 2.77219C9.53053 2.54277 9.64583 2.24276 9.64583 1.9404C9.64583 1.63804 9.53053 1.33803 9.30111 1.10861C8.84228 0.649771 8.09755 0.649771 7.63754 1.10861Z" fill="currentColor" /></symbol>
<symbol id="close" viewBox="0 0 16 16"><path d="M9.38526 8.08753L15.5498 1.85558C15.9653 1.43545 15.9653 0.805252 15.5498 0.385121C15.1342 -0.0350102 14.5108 -0.0350102 14.0952 0.385121L7.93072 6.61707L1.76623 0.315098C1.35065 -0.105033 0.727273 -0.105033 0.311688 0.315098C-0.103896 0.73523 -0.103896 1.36543 0.311688 1.78556L6.47618 8.0175L0.311688 14.2495C-0.103896 14.6696 -0.103896 15.2998 0.311688 15.7199C0.519481 15.93 0.796499 16 1.07355 16C1.35061 16 1.62769 15.93 1.83548 15.7199L7.99997 9.48797L14.1645 15.7199C14.3722 15.93 14.6493 16 14.9264 16C15.2034 16 15.4805 15.93 15.6883 15.7199C16.1039 15.2998 16.1039 14.6696 15.6883 14.2495L9.38526 8.08753Z" fill="currentColor"/></symbol>
<symbol id="copy" viewBox="0 0 24 24" fill="none"><path d="M20 6H8a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2Zm0 13a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v10Z" fill="currentColor"/><path d="M4 5a1 1 0 0 1 1-1h12a1 1 0 1 0 0-2H4a2 2 0 0 0-2 2v13a1 1 0 1 0 2 0V5Z" fill="currentColor"/></symbol>
<symbol id="caret-right" viewBox="0 0 24 24"><path d="M9.5 17L14.5 12L9.5 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></symbol>
<symbol id="caret-down" viewBox="0 0 24 24"><path d="M7 9.5L12 14.5L17 9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></symbol>
<symbol id="new-store" viewBox="0 0 32 32"><path d="M16 10V22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 16H10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle fill="none" cx="16" cy="16" r="15" stroke="currentColor" stroke-width="2"/></symbol>

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,7 +1,7 @@
const confirmCopy = (el, message) => {
el.innerText = message;
setTimeout(function () {
el.innerText = el.dataset.clipboardInitialText;
el.innerHTML = el.dataset.clipboardInitial;
}, 2500);
}
@ -10,8 +10,8 @@ window.copyToClipboard = function (e, data) {
const item = e.target.closest('[data-clipboard]');
const confirm = item.querySelector('[data-clipboard-confirm]') || item;
const message = confirm.getAttribute('data-clipboard-confirm') || 'Copied ✔';
if (!confirm.dataset.clipboardInitialText) {
confirm.dataset.clipboardInitialText = confirm.innerText;
if (!confirm.dataset.clipboardInitial) {
confirm.dataset.clipboardInitial = confirm.innerHTML;
confirm.style.minWidth = confirm.getBoundingClientRect().width + 'px';
}
if (navigator.clipboard) {

View file

@ -19,7 +19,7 @@
--icon-size: 1.5rem;
height: var(--header-height);
z-index: 9999999;
z-index: 1031; /* needs a value between fixed and the offcanvas backdrop, see https://getbootstrap.com/docs/5.1/layout/z-index/ */
}
#mainMenuHead {

View file

@ -11,7 +11,7 @@
.qr-container {
position: relative;
text-align: center;
display: inline-block;
}
.qr-container svg {

View file

@ -65,6 +65,11 @@ hr.primary {
}
}
a.unobtrusive-link {
color: inherit;
text-decoration: inherit;
}
/* Info icons in main headline */
h2 small .fa-question-circle-o {
position: relative;