Fix: Dashboard UI issue on mobile (#7223)
What changed, and why it matters
This commit is a user-interface fix for the BTCPay Server dashboard on mobile devices. It simplifies how the wallet balance widget is rendered, removes a multi-column dashboard layout, and makes the default cryptocurrency optional. There is no security-relevant change.
No security action required; treat as a routine UI/layout fix.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors StoreWalletBalance.cs to use primary constructor syntax, removes an unused StoreRepository dependency, and makes the cryptoCode parameter optional (defaulting to the network’s default crypto code). Dashboard.cshtml is restructured to render a single wallet balance widget instead of iterating over EnabledWalletCryptos, and site.css removes the flexbox-based dashboard-row layout rules. These are purely presentational/structural changes.
Changed components
BTCPayServer/Components/StoreWalletBalance/StoreWalletBalance.csBTCPayServer/Views/UIStores/Dashboard.cshtmlBTCPayServer/wwwroot/main/site.cssInspect captured patch +48 / −99
diff --git a/BTCPayServer/Components/StoreWalletBalance/StoreWalletBalance.cs b/BTCPayServer/Components/StoreWalletBalance/StoreWalletBalance.cs
index b27cae1..67e9bba 100644
--- a/BTCPayServer/Components/StoreWalletBalance/StoreWalletBalance.cs
+++ b/BTCPayServer/Components/StoreWalletBalance/StoreWalletBalance.cs
@@ -6,51 +6,37 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
-using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.ViewComponents;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Components.StoreWalletBalance;
-public class StoreWalletBalance : ViewComponent
+public class StoreWalletBalance(
+ CurrencyNameTable currencies,
+ WalletHistogramService walletHistogramService,
+ BTCPayWalletProvider walletProvider,
+ BTCPayNetworkProvider networkProvider,
+ PaymentMethodHandlerDictionary handlers)
+ : ViewComponent
{
private const HistogramType DefaultType = HistogramType.Week;
-
- private readonly StoreRepository _storeRepo;
- private readonly CurrencyNameTable _currencies;
- private readonly WalletHistogramService _walletHistogramService;
- private readonly BTCPayWalletProvider _walletProvider;
- private readonly BTCPayNetworkProvider _networkProvider;
- private readonly PaymentMethodHandlerDictionary _handlers;
-
- public StoreWalletBalance(
- StoreRepository storeRepo,
- CurrencyNameTable currencies,
- WalletHistogramService walletHistogramService,
- BTCPayWalletProvider walletProvider,
- BTCPayNetworkProvider networkProvider,
- PaymentMethodHandlerDictionary handlers)
- {
- _storeRepo = storeRepo;
- _currencies = currencies;
- _walletProvider = walletProvider;
- _networkProvider = networkProvider;
- _walletHistogramService = walletHistogramService;
- _handlers = handlers;
- }
-
- public async Task<IViewComponentResult> InvokeAsync(StoreData store, string cryptoCode)
+ public async Task<IViewComponentResult> InvokeAsync(StoreData store, string? cryptoCode = null)
{
+ cryptoCode ??= networkProvider.DefaultNetwork?.CryptoCode;
+ if (cryptoCode is null)
+ return new HtmlContentViewComponentResult(new StringHtmlContent(string.Empty));
var walletId = new WalletId(store.Id, cryptoCode);
- var data = await _walletHistogramService.GetHistogram(store, walletId, DefaultType);
+ var data = await walletHistogramService.GetHistogram(store, walletId, DefaultType);
var defaultCurrency = store.GetStoreBlob().DefaultCurrency;
var vm = new StoreWalletBalanceViewModel
{
StoreId = store.Id,
CryptoCode = cryptoCode,
- CurrencyData = _currencies.GetCurrencyData(defaultCurrency, true),
+ CurrencyData = currencies.GetCurrencyData(defaultCurrency, true),
DefaultCurrency = defaultCurrency,
WalletId = walletId,
Type = DefaultType
@@ -65,9 +51,9 @@ public class StoreWalletBalance : ViewComponent
else
{
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(3));
- var wallet = _walletProvider.GetWallet(cryptoCode);
- var derivation = store.GetDerivationSchemeSettings(_handlers, walletId.CryptoCode);
- var handler = _handlers.TryGetBitcoinHandler(walletId.CryptoCode);
+ var wallet = walletProvider.GetWallet(cryptoCode);
+ var derivation = store.GetDerivationSchemeSettings(handlers, walletId.CryptoCode);
+ var handler = handlers.TryGetBitcoinHandler(walletId.CryptoCode);
if (wallet is not null && derivation is not null && handler is not null)
{
var balance = await wallet.GetBalance(derivation.AccountDerivation, cts.Token);
diff --git a/BTCPayServer/Views/UIStores/Dashboard.cshtml b/BTCPayServer/Views/UIStores/Dashboard.cshtml
index f3d7f4a..4b54f54 100644
--- a/BTCPayServer/Views/UIStores/Dashboard.cshtml
+++ b/BTCPayServer/Views/UIStores/Dashboard.cshtml
@@ -46,55 +46,46 @@
</script>
<div id="Dashboard">
<vc:ui-extension-point location="dashboard" model="@Model" />
- @if (Model.WalletEnabled && Model.EnabledWalletCryptos.Count > 0)
+ @if (Model.WalletEnabled)
{
- <div class="dashboard-row wallet-balances">
- @foreach (var walletCryptoCode in Model.EnabledWalletCryptos)
- {
- <vc:store-wallet-balance store="@store" crypto-code="@walletCryptoCode" />
- }
- </div>
+ <vc:store-wallet-balance store="@store" />
}
- <div class="dashboard-row dashboard-row-secondary">
- @if (!Model.WalletEnabled)
- {
- <div class="widget setup-guide">
- <header>
- <h5 class="mb-4 text-muted" text-translate="true">This store is ready to accept transactions, good job!</h5>
- </header>
- <div class="list-group" id="SetupGuide">
- <div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone">
- <vc:icon symbol="done" />
- <div class="content">
- <h5 class="mb-0 text-success" text-translate="true">Set up a Lightning node</h5>
- </div>
+ else
+ {
+ <div class="widget setup-guide">
+ <header>
+ <h5 class="mb-4 text-muted" text-translate="true">This store is ready to accept transactions, good job!</h5>
+ </header>
+ <div class="list-group" id="SetupGuide">
+ <div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone">
+ <vc:icon symbol="done" />
+ <div class="content">
+ <h5 class="mb-0 text-success" text-translate="true">Set up a Lightning node</h5>
</div>
- <a asp-controller="UIStores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
- id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center">
- <vc:icon symbol="wallet-new" />
- <div class="content">
- <h5 class="mb-0" text-translate="true">Set up a wallet</h5>
- </div>
- <vc:icon symbol="caret-right" />
- </a>
- <vc:ui-extension-point location="dashboard-setup-guide-payment" model="@Model" />
</div>
+ <a asp-controller="UIStores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
+ id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center">
+ <vc:icon symbol="wallet-new" />
+ <div class="content">
+ <h5 class="mb-0" text-translate="true">Set up a wallet</h5>
+ </div>
+ <vc:icon symbol="caret-right" />
+ </a>
+ <vc:ui-extension-point location="dashboard-setup-guide-payment" model="@Model" />
</div>
- }
- <vc:store-numbers store="store" crypto-code="@Model.CryptoCode" initial-rendering="true" />
- @if (Model.WalletEnabled)
- {
- <vc:store-recent-transactions store="store" crypto-code="@Model.CryptoCode" initial-rendering="true" />
- }
- </div>
- <div class="dashboard-row dashboard-row-tertiary">
- <vc:store-recent-invoices store="store" initial-rendering="true" />
- </div>
+ </div>
+ }
+ <vc:store-numbers store="store" crypto-code="@Model.CryptoCode" initial-rendering="true" />
@if (Model.LightningEnabled)
{
<vc:store-lightning-balance store="store" crypto-code="@Model.CryptoCode" initial-rendering="true" />
<vc:store-lightning-services store="store" crypto-code="@Model.CryptoCode" permission="@Policies.CanModifyServerSettings" />
}
+ @if (Model.WalletEnabled)
+ {
+ <vc:store-recent-transactions store="store" crypto-code="@Model.CryptoCode" initial-rendering="true" />
+ }
+ <vc:store-recent-invoices store="store" crypto-code="@Model.CryptoCode" initial-rendering="true" />
@foreach (var app in Model.Apps)
{
<vc:app-sales app-id="@app.Id" app-type="@app.AppType" />
diff --git a/BTCPayServer/wwwroot/main/site.css b/BTCPayServer/wwwroot/main/site.css
index 5ed9147..7b99c83 100644
--- a/BTCPayServer/wwwroot/main/site.css
+++ b/BTCPayServer/wwwroot/main/site.css
@@ -375,34 +375,6 @@ h2 .icon.icon-info {
grid-template-columns: repeat(12, 1fr);
}
-#Dashboard .dashboard-row {
- display: flex;
- flex-wrap: wrap;
- gap: var(--btcpay-space-m);
- grid-column-start: 1;
- grid-column-end: 13;
-}
-
-#Dashboard .dashboard-row .widget {
- flex: 1 1 320px;
-}
-
-#Dashboard .dashboard-row.wallet-balances .widget {
- flex: 1 1 420px;
-}
-
-#Dashboard .dashboard-row.dashboard-row-secondary .widget.store-numbers {
- flex: 1 1 320px;
-}
-
-#Dashboard .dashboard-row.dashboard-row-secondary .widget.store-recent-transactions {
- flex: 2 1 520px;
-}
-
-#Dashboard .dashboard-row.dashboard-row-tertiary .widget {
- flex: 1 1 100%;
-}
-
.widget {
--widget-padding: var(--btcpay-space-m);
--widget-chart-width: 100vw;
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.