diff --git a/BTCPayServer/Components/AppSales/AppSales.cs b/BTCPayServer/Components/AppSales/AppSales.cs index 511908109..ce3bb8e5f 100644 --- a/BTCPayServer/Components/AppSales/AppSales.cs +++ b/BTCPayServer/Components/AppSales/AppSales.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Models.AppViewModels; @@ -8,26 +9,30 @@ using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Components.AppSales; +public enum AppSalesPeriod +{ + Week, + Month +} + public class AppSales : ViewComponent { private readonly AppService _appService; - private readonly StoreRepository _storeRepo; - public AppSales(AppService appService, StoreRepository storeRepo) + public AppSales(AppService appService) { _appService = appService; - _storeRepo = storeRepo; } - public async Task InvokeAsync(AppData app) + public async Task InvokeAsync(AppSalesViewModel vm) { - var stats = await _appService.GetSalesStats(app); - var vm = new AppSalesViewModel - { - App = app, - SalesCount = stats.SalesCount, - Series = stats.Series - }; + if (vm.App == null) throw new ArgumentNullException(nameof(vm.App)); + if (vm.InitialRendering) return View(vm); + + var stats = await _appService.GetSalesStats(vm.App); + + vm.SalesCount = stats.SalesCount; + vm.Series = stats.Series; return View(vm); } diff --git a/BTCPayServer/Components/AppSales/AppSalesViewModel.cs b/BTCPayServer/Components/AppSales/AppSalesViewModel.cs index 19f64fd6c..36902ebfa 100644 --- a/BTCPayServer/Components/AppSales/AppSalesViewModel.cs +++ b/BTCPayServer/Components/AppSales/AppSalesViewModel.cs @@ -7,6 +7,8 @@ namespace BTCPayServer.Components.AppSales; public class AppSalesViewModel { public AppData App { get; set; } + public AppSalesPeriod Period { get; set; } = AppSalesPeriod.Week; public int SalesCount { get; set; } public IEnumerable Series { get; set; } + public bool InitialRendering { get; set; } } diff --git a/BTCPayServer/Components/AppSales/Default.cshtml b/BTCPayServer/Components/AppSales/Default.cshtml index 5c58ddbd9..9f7dfefeb 100644 --- a/BTCPayServer/Components/AppSales/Default.cshtml +++ b/BTCPayServer/Components/AppSales/Default.cshtml @@ -1,6 +1,7 @@ @using BTCPayServer.Services.Apps @using BTCPayServer.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer.Components.AppSales @model BTCPayServer.Components.AppSales.AppSalesViewModel @{ @@ -13,22 +14,81 @@

@Model.App.Name @label

Manage -

@Model.SalesCount Total @label

-
- + @if (Model.InitialRendering) + { +
+
+ Loading... +
+
+ + } + else + { +
+ + @Model.SalesCount Total @label + +
+ + + + +
+
+
+ + } diff --git a/BTCPayServer/Components/AppTopItems/AppTopItems.cs b/BTCPayServer/Components/AppTopItems/AppTopItems.cs index f9c608dba..1140564ad 100644 --- a/BTCPayServer/Components/AppTopItems/AppTopItems.cs +++ b/BTCPayServer/Components/AppTopItems/AppTopItems.cs @@ -12,24 +12,22 @@ namespace BTCPayServer.Components.AppTopItems; public class AppTopItems : ViewComponent { private readonly AppService _appService; - private readonly StoreRepository _storeRepo; - public AppTopItems(AppService appService, StoreRepository storeRepo) + public AppTopItems(AppService appService) { _appService = appService; - _storeRepo = storeRepo; } - public async Task InvokeAsync(AppData app) + public async Task InvokeAsync(AppTopItemsViewModel vm) { - var entries = Enum.Parse(app.AppType) == AppType.Crowdfund - ? await _appService.GetPerkStats(app) - : await _appService.GetItemStats(app); - var vm = new AppTopItemsViewModel - { - App = app, - Entries = entries.ToList() - }; + if (vm.App == null) throw new ArgumentNullException(nameof(vm.App)); + if (vm.InitialRendering) return View(vm); + + var entries = Enum.Parse(vm.App.AppType) == AppType.Crowdfund + ? await _appService.GetPerkStats(vm.App) + : await _appService.GetItemStats(vm.App); + + vm.Entries = entries.ToList(); return View(vm); } diff --git a/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs b/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs index f1916700d..60bacb425 100644 --- a/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs +++ b/BTCPayServer/Components/AppTopItems/AppTopItemsViewModel.cs @@ -8,4 +8,5 @@ public class AppTopItemsViewModel { public AppData App { get; set; } public List Entries { get; set; } + public bool InitialRendering { get; set; } } diff --git a/BTCPayServer/Components/AppTopItems/Default.cshtml b/BTCPayServer/Components/AppTopItems/Default.cshtml index 61bb98059..e2a19f387 100644 --- a/BTCPayServer/Components/AppTopItems/Default.cshtml +++ b/BTCPayServer/Components/AppTopItems/Default.cshtml @@ -11,7 +11,27 @@

Top @(Model.App.AppType == nameof(AppType.Crowdfund) ? "Perks" : "Items")

View All - @if (Model.Entries.Any()) + @if (Model.InitialRendering) + { +
+
+ Loading... +
+
+ + } + else if (Model.Entries.Any()) {
- diff --git a/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalance.cs b/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalance.cs index b29a976a1..4ebad43d9 100644 --- a/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalance.cs +++ b/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalance.cs @@ -19,7 +19,6 @@ namespace BTCPayServer.Components.StoreLightningBalance; public class StoreLightningBalance : ViewComponent { - private string _cryptoCode; private readonly StoreRepository _storeRepo; private readonly CurrencyNameTable _currencies; private readonly BTCPayServerOptions _btcpayServerOptions; @@ -44,14 +43,13 @@ public class StoreLightningBalance : ViewComponent _externalServiceOptions = externalServiceOptions; _lightningClientFactory = lightningClientFactory; _lightningNetworkOptions = lightningNetworkOptions; - _cryptoCode = _networkProvider.DefaultNetwork.CryptoCode; } public async Task InvokeAsync(StoreLightningBalanceViewModel vm) { if (vm.Store == null) throw new ArgumentNullException(nameof(vm.Store)); + if (vm.CryptoCode == null) throw new ArgumentNullException(nameof(vm.CryptoCode)); - vm.CryptoCode = _cryptoCode; vm.DefaultCurrency = vm.Store.GetStoreBlob().DefaultCurrency; vm.CurrencyData = _currencies.GetCurrencyData(vm.DefaultCurrency, true); @@ -59,7 +57,7 @@ public class StoreLightningBalance : ViewComponent try { - var lightningClient = GetLightningClient(vm.Store); + var lightningClient = GetLightningClient(vm.Store, vm.CryptoCode); var balance = await lightningClient.GetBalance(); vm.Balance = balance; vm.TotalOnchain = balance.OnchainBalance != null @@ -84,10 +82,10 @@ public class StoreLightningBalance : ViewComponent return View(vm); } - private ILightningClient GetLightningClient(StoreData store) + private ILightningClient GetLightningClient(StoreData store, string cryptoCode) { - var network = _networkProvider.GetNetwork(_cryptoCode); - var id = new PaymentMethodId(_cryptoCode, PaymentTypes.LightningLike); + var network = _networkProvider.GetNetwork(cryptoCode); + var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike); var existing = store.GetSupportedPaymentMethods(_networkProvider) .OfType() .FirstOrDefault(d => d.PaymentId == id); @@ -97,7 +95,7 @@ public class StoreLightningBalance : ViewComponent { return _lightningClientFactory.Create(connectionString, network); } - if (existing.IsInternalNode && _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(_cryptoCode, out var internalLightningNode)) + if (existing.IsInternalNode && _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var internalLightningNode)) { return _lightningClientFactory.Create(internalLightningNode, network); } diff --git a/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalanceViewModel.cs b/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalanceViewModel.cs index 32c4f8b8b..0bb0b5c86 100644 --- a/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalanceViewModel.cs +++ b/BTCPayServer/Components/StoreLightningBalance/StoreLightningBalanceViewModel.cs @@ -1,8 +1,6 @@ -using BTCPayServer.Client.JsonConverters; using BTCPayServer.Data; using BTCPayServer.Lightning; using BTCPayServer.Services.Rates; -using Newtonsoft.Json; namespace BTCPayServer.Components.StoreLightningBalance; diff --git a/BTCPayServer/Components/StoreLightningServices/StoreLightningServices.cs b/BTCPayServer/Components/StoreLightningServices/StoreLightningServices.cs index ab4ebdbc7..c7a3f81a0 100644 --- a/BTCPayServer/Components/StoreLightningServices/StoreLightningServices.cs +++ b/BTCPayServer/Components/StoreLightningServices/StoreLightningServices.cs @@ -18,7 +18,6 @@ namespace BTCPayServer.Components.StoreLightningServices; public class StoreLightningServices : ViewComponent { - private readonly string _cryptoCode; private readonly BTCPayServerOptions _btcpayServerOptions; private readonly BTCPayNetworkProvider _networkProvider; private readonly IOptions _externalServiceOptions; @@ -31,61 +30,55 @@ public class StoreLightningServices : ViewComponent _networkProvider = networkProvider; _btcpayServerOptions = btcpayServerOptions; _externalServiceOptions = externalServiceOptions; - _cryptoCode = _networkProvider.DefaultNetwork.CryptoCode; } - public IViewComponentResult Invoke(StoreData store) + public IViewComponentResult Invoke(StoreLightningServicesViewModel vm) { - var vm = new StoreLightningServicesViewModel - { - Store = store, - CryptoCode = _cryptoCode, - }; + if (vm.Store == null) throw new ArgumentNullException(nameof(vm.Store)); + if (vm.CryptoCode == null) throw new ArgumentNullException(nameof(vm.CryptoCode)); + if (vm.LightningNodeType != LightningNodeType.Internal) return View(vm); - if (vm.LightningNodeType == LightningNodeType.Internal) - { - var services = _externalServiceOptions.Value.ExternalServices.ToList() - .Where(service => ExternalServices.LightningServiceTypes.Contains(service.Type)) - .Select(async service => - { - var model = new AdditionalServiceViewModel - { - DisplayName = service.DisplayName, - ServiceName = service.ServiceName, - CryptoCode = service.CryptoCode, - Type = service.Type.ToString() - }; - try - { - model.Link = await service.GetLink(Request.GetAbsoluteUriNoPathBase(), _btcpayServerOptions.NetworkType); - } - catch (Exception exception) - { - model.Error = exception.Message; - } - return model; - }) - .Select(t => t.Result) - .ToList(); - - // other services - foreach ((string key, Uri value) in _externalServiceOptions.Value.OtherExternalServices) + var services = _externalServiceOptions.Value.ExternalServices.ToList() + .Where(service => ExternalServices.LightningServiceTypes.Contains(service.Type)) + .Select(async service => { - if (ExternalServices.LightningServiceNames.Contains(key)) + var model = new AdditionalServiceViewModel { - services.Add(new AdditionalServiceViewModel - { - DisplayName = key, - ServiceName = key, - Type = key.Replace(" ", ""), - Link = Request.GetAbsoluteUriNoPathBase(value).AbsoluteUri - }); + DisplayName = service.DisplayName, + ServiceName = service.ServiceName, + CryptoCode = service.CryptoCode, + Type = service.Type.ToString() + }; + try + { + model.Link = await service.GetLink(Request.GetAbsoluteUriNoPathBase(), _btcpayServerOptions.NetworkType); } + catch (Exception exception) + { + model.Error = exception.Message; + } + return model; + }) + .Select(t => t.Result) + .ToList(); + + // other services + foreach ((string key, Uri value) in _externalServiceOptions.Value.OtherExternalServices) + { + if (ExternalServices.LightningServiceNames.Contains(key)) + { + services.Add(new AdditionalServiceViewModel + { + DisplayName = key, + ServiceName = key, + Type = key.Replace(" ", ""), + Link = Request.GetAbsoluteUriNoPathBase(value).AbsoluteUri + }); } - - vm.Services = services; } + vm.Services = services; + return View(vm); } } diff --git a/BTCPayServer/Components/StoreNumbers/Default.cshtml b/BTCPayServer/Components/StoreNumbers/Default.cshtml index ab03ab37b..89e5de5f6 100644 --- a/BTCPayServer/Components/StoreNumbers/Default.cshtml +++ b/BTCPayServer/Components/StoreNumbers/Default.cshtml @@ -1,30 +1,51 @@ @model BTCPayServer.Components.StoreNumbers.StoreNumbersViewModel -
-
-
-
Payouts Pending
- Manage -
-
@Model.PayoutsPending
-
- @if (Model.Transactions is not null) - { -
-
-
TXs in the last @Model.TransactionDays days
- @if (Model.Transactions.Value > 0) - { - View All - } -
-
@Model.Transactions.Value
-
- } -
-
-
Refunds Issued
-
-
@Model.RefundsIssued
-
+
+ @if (Model.InitialRendering) + { +
+
+ Loading... +
+
+ + } + else + { +
+
+
Payouts Pending
+ Manage +
+
@Model.PayoutsPending
+
+ @if (Model.Transactions is not null) + { +
+
+
TXs in the last @Model.TransactionDays days
+ @if (Model.Transactions.Value > 0) + { + View All + } +
+
@Model.Transactions.Value
+
+ } +
+
+
Refunds Issued
+
+
@Model.RefundsIssued
+
+ }
diff --git a/BTCPayServer/Components/StoreNumbers/StoreNumbers.cs b/BTCPayServer/Components/StoreNumbers/StoreNumbers.cs index dde820a87..d16857913 100644 --- a/BTCPayServer/Components/StoreNumbers/StoreNumbers.cs +++ b/BTCPayServer/Components/StoreNumbers/StoreNumbers.cs @@ -19,9 +19,6 @@ namespace BTCPayServer.Components.StoreNumbers; public class StoreNumbers : ViewComponent { - private string CryptoCode; - private const int TransactionDays = 7; - private readonly StoreRepository _storeRepo; private readonly ApplicationDbContextFactory _dbContextFactory; private readonly BTCPayWalletProvider _walletProvider; @@ -40,41 +37,39 @@ public class StoreNumbers : ViewComponent _nbxConnectionFactory = nbxConnectionFactory; _networkProvider = networkProvider; _dbContextFactory = dbContextFactory; - CryptoCode = networkProvider.DefaultNetwork.CryptoCode; } - public async Task InvokeAsync(StoreData store) + public async Task InvokeAsync(StoreNumbersViewModel vm) { + if (vm.Store == null) throw new ArgumentNullException(nameof(vm.Store)); + if (vm.CryptoCode == null) throw new ArgumentNullException(nameof(vm.CryptoCode)); + + vm.WalletId = new WalletId(vm.Store.Id, vm.CryptoCode); + + if (vm.InitialRendering) return View(vm); await using var ctx = _dbContextFactory.CreateContext(); var payoutsCount = await ctx.Payouts - .Where(p => p.PullPaymentData.StoreId == store.Id && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingApproval) + .Where(p => p.PullPaymentData.StoreId == vm.Store.Id && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingApproval) .CountAsync(); var refundsCount = await ctx.Invoices - .Where(i => i.StoreData.Id == store.Id && !i.Archived && i.CurrentRefundId != null) + .Where(i => i.StoreData.Id == vm.Store.Id && !i.Archived && i.CurrentRefundId != null) .CountAsync(); - var walletId = new WalletId(store.Id, CryptoCode); - var derivation = store.GetDerivationSchemeSettings(_networkProvider, walletId.CryptoCode); + var derivation = vm.Store.GetDerivationSchemeSettings(_networkProvider, vm.CryptoCode); int? transactionsCount = null; if (derivation != null && _nbxConnectionFactory.Available) { await using var conn = await _nbxConnectionFactory.OpenConnection(); var wid = NBXplorer.Client.DBUtils.nbxv1_get_wallet_id(derivation.Network.CryptoCode, derivation.AccountDerivation.ToString()); - var afterDate = DateTimeOffset.UtcNow - TimeSpan.FromDays(TransactionDays); + var afterDate = DateTimeOffset.UtcNow - TimeSpan.FromDays(vm.TransactionDays); var count = await conn.ExecuteScalarAsync("SELECT COUNT(*) FROM wallets_history WHERE code=@code AND wallet_id=@wid AND seen_at > @afterDate", new { code = derivation.Network.CryptoCode, wid, afterDate }); transactionsCount = (int)count; } - - var vm = new StoreNumbersViewModel - { - Store = store, - WalletId = walletId, - PayoutsPending = payoutsCount, - Transactions = transactionsCount, - TransactionDays = TransactionDays, - RefundsIssued = refundsCount - }; + + vm.PayoutsPending = payoutsCount; + vm.Transactions = transactionsCount; + vm.RefundsIssued = refundsCount; return View(vm); } diff --git a/BTCPayServer/Components/StoreNumbers/StoreNumbersViewModel.cs b/BTCPayServer/Components/StoreNumbers/StoreNumbersViewModel.cs index 5546d1d7a..e234219f7 100644 --- a/BTCPayServer/Components/StoreNumbers/StoreNumbersViewModel.cs +++ b/BTCPayServer/Components/StoreNumbers/StoreNumbersViewModel.cs @@ -1,4 +1,3 @@ -using System.Collections; using BTCPayServer.Data; namespace BTCPayServer.Components.StoreNumbers; @@ -10,5 +9,7 @@ public class StoreNumbersViewModel public int PayoutsPending { get; set; } public int? Transactions { get; set; } public int RefundsIssued { get; set; } - public int TransactionDays { get; set; } + public int TransactionDays { get; set; } = 7; + public bool InitialRendering { get; set; } + public string CryptoCode { get; set; } } diff --git a/BTCPayServer/Components/StoreRecentInvoices/Default.cshtml b/BTCPayServer/Components/StoreRecentInvoices/Default.cshtml index 8fbbb156e..8a7f33b54 100644 --- a/BTCPayServer/Components/StoreRecentInvoices/Default.cshtml +++ b/BTCPayServer/Components/StoreRecentInvoices/Default.cshtml @@ -3,7 +3,7 @@ @using BTCPayServer.Services.Invoices @model BTCPayServer.Components.StoreRecentInvoices.StoreRecentInvoicesViewModel -
+

Recent Invoices

@if (Model.Invoices.Any()) @@ -11,7 +11,25 @@ View All }
- @if (Model.Invoices.Any()) + @if (Model.InitialRendering) + { +
+
+ Loading... +
+
+ + } + else if (Model.Invoices.Any()) { diff --git a/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoices.cs b/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoices.cs index c4815ac47..0e171d039 100644 --- a/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoices.cs +++ b/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoices.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -32,22 +33,25 @@ public class StoreRecentInvoices : ViewComponent _dbContextFactory = dbContextFactory; } - public async Task InvokeAsync(StoreData store) + public async Task InvokeAsync(StoreRecentInvoicesViewModel vm) { + if (vm.Store == null) throw new ArgumentNullException(nameof(vm.Store)); + if (vm.CryptoCode == null) throw new ArgumentNullException(nameof(vm.CryptoCode)); + if (vm.InitialRendering) return View(vm); + var userId = _userManager.GetUserId(UserClaimsPrincipal); var invoiceEntities = await _invoiceRepo.GetInvoices(new InvoiceQuery { UserId = userId, - StoreId = new [] { store.Id }, + StoreId = new [] { vm.Store.Id }, IncludeArchived = false, IncludeRefunds = true, Take = 5 }); - var invoices = new List(); - foreach (var invoice in invoiceEntities) - { - var state = invoice.GetInvoiceState(); - invoices.Add(new StoreRecentInvoiceViewModel + + vm.Invoices = (from invoice in invoiceEntities + let state = invoice.GetInvoiceState() + select new StoreRecentInvoiceViewModel { Date = invoice.InvoiceTime, Status = state, @@ -55,13 +59,7 @@ public class StoreRecentInvoices : ViewComponent InvoiceId = invoice.Id, OrderId = invoice.Metadata.OrderId ?? string.Empty, AmountCurrency = _currencyNameTable.DisplayFormatCurrency(invoice.Price, invoice.Currency), - }); - } - var vm = new StoreRecentInvoicesViewModel - { - Store = store, - Invoices = invoices - }; + }).ToList(); return View(vm); } diff --git a/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoicesViewModel.cs b/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoicesViewModel.cs index feee7f9c0..339ca997c 100644 --- a/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoicesViewModel.cs +++ b/BTCPayServer/Components/StoreRecentInvoices/StoreRecentInvoicesViewModel.cs @@ -6,5 +6,7 @@ namespace BTCPayServer.Components.StoreRecentInvoices; public class StoreRecentInvoicesViewModel { public StoreData Store { get; set; } - public IEnumerable Invoices { get; set; } + public IList Invoices { get; set; } = new List(); + public bool InitialRendering { get; set; } + public string CryptoCode { get; set; } } diff --git a/BTCPayServer/Components/StoreRecentTransactions/Default.cshtml b/BTCPayServer/Components/StoreRecentTransactions/Default.cshtml index 7d3c8a4c9..e9888fd0c 100644 --- a/BTCPayServer/Components/StoreRecentTransactions/Default.cshtml +++ b/BTCPayServer/Components/StoreRecentTransactions/Default.cshtml @@ -1,7 +1,7 @@ @using BTCPayServer.Abstractions.Extensions @model BTCPayServer.Components.StoreRecentTransactions.StoreRecentTransactionsViewModel -
+

Recent Transactions

@if (Model.Transactions.Any()) @@ -9,7 +9,25 @@ View All }
- @if (Model.Transactions.Any()) + @if (Model.InitialRendering) + { +
+
+ Loading... +
+
+ + } + else if (Model.Transactions.Any()) {
diff --git a/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactions.cs b/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactions.cs index 0f29e1f4c..11b5562a0 100644 --- a/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactions.cs +++ b/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactions.cs @@ -21,32 +21,27 @@ namespace BTCPayServer.Components.StoreRecentTransactions; public class StoreRecentTransactions : ViewComponent { - private string CryptoCode; - private readonly StoreRepository _storeRepo; - private readonly ApplicationDbContextFactory _dbContextFactory; private readonly BTCPayWalletProvider _walletProvider; - public BTCPayNetworkProvider NetworkProvider { get; } public StoreRecentTransactions( - StoreRepository storeRepo, BTCPayNetworkProvider networkProvider, - NBXplorerConnectionFactory connectionFactory, - BTCPayWalletProvider walletProvider, - ApplicationDbContextFactory dbContextFactory) + BTCPayWalletProvider walletProvider) { - _storeRepo = storeRepo; NetworkProvider = networkProvider; _walletProvider = walletProvider; - _dbContextFactory = dbContextFactory; - CryptoCode = networkProvider.DefaultNetwork.CryptoCode; } - - public async Task InvokeAsync(StoreData store) + public async Task InvokeAsync(StoreRecentTransactionsViewModel vm) { - var walletId = new WalletId(store.Id, CryptoCode); - var derivationSettings = store.GetDerivationSchemeSettings(NetworkProvider, walletId.CryptoCode); + if (vm.Store == null) throw new ArgumentNullException(nameof(vm.Store)); + if (vm.CryptoCode == null) throw new ArgumentNullException(nameof(vm.CryptoCode)); + + vm.WalletId = new WalletId(vm.Store.Id, vm.CryptoCode); + + if (vm.InitialRendering) return View(vm); + + var derivationSettings = vm.Store.GetDerivationSchemeSettings(NetworkProvider, vm.CryptoCode); var transactions = new List(); if (derivationSettings?.AccountDerivation is not null) { @@ -66,13 +61,8 @@ public class StoreRecentTransactions : ViewComponent .ToList(); } - - var vm = new StoreRecentTransactionsViewModel - { - Store = store, - WalletId = walletId, - Transactions = transactions - }; + vm.Transactions = transactions; + return View(vm); } } diff --git a/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactionsViewModel.cs b/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactionsViewModel.cs index dcb296da3..d8e1dbcd6 100644 --- a/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactionsViewModel.cs +++ b/BTCPayServer/Components/StoreRecentTransactions/StoreRecentTransactionsViewModel.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Collections.Generic; using BTCPayServer.Data; @@ -9,4 +8,6 @@ public class StoreRecentTransactionsViewModel public StoreData Store { get; set; } public IList Transactions { get; set; } = new List(); public WalletId WalletId { get; set; } + public bool InitialRendering { get; set; } + public string CryptoCode { get; set; } } diff --git a/BTCPayServer/Components/StoreWalletBalance/Default.cshtml b/BTCPayServer/Components/StoreWalletBalance/Default.cshtml index 897a26b4b..83ea08607 100644 --- a/BTCPayServer/Components/StoreWalletBalance/Default.cshtml +++ b/BTCPayServer/Components/StoreWalletBalance/Default.cshtml @@ -24,13 +24,13 @@ } @if (Model.Series != null) { -
- - - - - - +
+ + + + + +
} @@ -114,7 +114,7 @@ render(data); document.addEventListener('DOMContentLoaded', () => { - delegate('change', `#${id} [name="filter"]`, async e => { + delegate('change', `#${id} [name="StoreWalletBalancePeriod-${storeId}"]`, async e => { const type = e.target.value; await update(type); }) diff --git a/BTCPayServer/Controllers/UIAppsController.Dashboard.cs b/BTCPayServer/Controllers/UIAppsController.Dashboard.cs new file mode 100644 index 000000000..7dad6395d --- /dev/null +++ b/BTCPayServer/Controllers/UIAppsController.Dashboard.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading.Tasks; +using BTCPayServer.Components.AppSales; +using BTCPayServer.Components.AppTopItems; +using Microsoft.AspNetCore.Mvc; + +namespace BTCPayServer.Controllers +{ + public partial class UIAppsController + { + [HttpGet("{appId}/dashboard/app-top-items")] + public IActionResult AppTopItems(string appId) + { + var app = HttpContext.GetAppData(); + if (app == null) + return NotFound(); + + app.StoreData = GetCurrentStore(); + + var vm = new AppTopItemsViewModel { App = app }; + return ViewComponent("AppTopItems", new { vm }); + } + + [HttpGet("{appId}/dashboard/app-sales")] + public IActionResult AppSales(string appId) + { + var app = HttpContext.GetAppData(); + if (app == null) + return NotFound(); + + app.StoreData = GetCurrentStore(); + + var vm = new AppSalesViewModel { App = app }; + return ViewComponent("AppSales", new { vm }); + } + + [HttpGet("{appId}/dashboard/app-sales/{period}")] + public async Task AppSales(string appId, AppSalesPeriod period) + { + var app = HttpContext.GetAppData(); + if (app == null) + return NotFound(); + + app.StoreData = GetCurrentStore(); + + var days = period switch + { + AppSalesPeriod.Week => 7, + AppSalesPeriod.Month => 30, + _ => throw new ArgumentException($"AppSalesPeriod {period} does not exist.") + }; + var stats = await _appService.GetSalesStats(app, days); + + return stats == null + ? NotFound() + : Json(stats); + } + } +} diff --git a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs index 6bfd01216..5e80e0ddd 100644 --- a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs +++ b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs @@ -1,22 +1,13 @@ #nullable enable -using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Abstractions.Constants; -using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Components.StoreLightningBalance; -using BTCPayServer.Configuration; +using BTCPayServer.Components.StoreNumbers; +using BTCPayServer.Components.StoreRecentInvoices; +using BTCPayServer.Components.StoreRecentTransactions; using BTCPayServer.Data; -using BTCPayServer.Lightning; -using BTCPayServer.Logging; -using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; -using BTCPayServer.Payments; -using BTCPayServer.Payments.Lightning; -using BTCPayServer.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; namespace BTCPayServer.Controllers { @@ -39,36 +30,66 @@ namespace BTCPayServer.Controllers LightningEnabled = lightningEnabled, StoreId = CurrentStore.Id, StoreName = CurrentStore.StoreName, + CryptoCode = _NetworkProvider.DefaultNetwork.CryptoCode, IsSetUp = walletEnabled || lightningEnabled }; // Widget data - if (vm.WalletEnabled || vm.LightningEnabled) - { - var userId = GetUserId(); - var apps = await _appService.GetAllApps(userId, false, store.Id); - vm.Apps = apps - .Select(a => - { - var appData = _appService.GetAppDataIfOwner(userId, a.Id).Result; - appData.StoreData = store; - return appData; - }) - .ToList(); - } + if (!vm.WalletEnabled && !vm.LightningEnabled) return View(vm); + var userId = GetUserId(); + var apps = await _appService.GetAllApps(userId, false, store.Id); + foreach (var app in apps) + { + var appData = await _appService.GetAppDataIfOwner(userId, app.Id); + vm.Apps.Add(appData); + } + return View(vm); } - - [HttpGet("{storeId}/lightning/{cryptoCode}/balance")] + + [HttpGet("{storeId}/dashboard/{cryptoCode}/lightning/balance")] public IActionResult LightningBalance(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); if (store == null) return NotFound(); - var vm = new StoreLightningBalanceViewModel { Store = store }; + var vm = new StoreLightningBalanceViewModel { Store = store, CryptoCode = cryptoCode }; return ViewComponent("StoreLightningBalance", new { vm }); } + + [HttpGet("{storeId}/dashboard/{cryptoCode}/numbers")] + public IActionResult StoreNumbers(string storeId, string cryptoCode) + { + var store = HttpContext.GetStoreData(); + if (store == null) + return NotFound(); + + var vm = new StoreNumbersViewModel { Store = store, CryptoCode = cryptoCode }; + return ViewComponent("StoreNumbers", new { vm }); + } + + [HttpGet("{storeId}/dashboard/{cryptoCode}/recent-transactions")] + public IActionResult RecentTransactions(string storeId, string cryptoCode) + { + var store = HttpContext.GetStoreData(); + if (store == null) + return NotFound(); + + var vm = new StoreRecentTransactionsViewModel { Store = store, CryptoCode = cryptoCode }; + return ViewComponent("StoreRecentTransactions", new { vm }); + } + + [HttpGet("{storeId}/dashboard/{cryptoCode}/recent-invoices")] + public IActionResult RecentInvoices(string storeId, string cryptoCode) + { + var store = HttpContext.GetStoreData(); + if (store == null) + return NotFound(); + + var vm = new StoreRecentInvoicesViewModel { Store = store, CryptoCode = cryptoCode }; + return ViewComponent("StoreRecentInvoices", new { vm }); + } } } diff --git a/BTCPayServer/Models/StoreViewModels/StoreDashboardViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreDashboardViewModel.cs index 70add6b4f..4df990c60 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreDashboardViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreDashboardViewModel.cs @@ -6,9 +6,10 @@ namespace BTCPayServer.Models.StoreViewModels; public class StoreDashboardViewModel { public string StoreId { get; set; } + public string CryptoCode { get; set; } public string StoreName { get; set; } public bool WalletEnabled { get; set; } public bool LightningEnabled { get; set; } public bool IsSetUp { get; set; } - public List Apps { get; set; } + public List Apps { get; set; } = new(); } diff --git a/BTCPayServer/Services/Apps/AppService.cs b/BTCPayServer/Services/Apps/AppService.cs index c0be47787..fc508728a 100644 --- a/BTCPayServer/Services/Apps/AppService.cs +++ b/BTCPayServer/Services/Apps/AppService.cs @@ -283,12 +283,7 @@ namespace BTCPayServer.Services.Apps var invoices = await GetInvoicesForApp(app); var paidInvoices = invoices.Where(IsPaid).ToArray(); var series = paidInvoices - .Where(entity => entity.InvoiceTime > DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays) && ( - // The POS data is present for the cart view, where multiple items can be bought - !string.IsNullOrEmpty(entity.Metadata.PosData) || - // The item code should be present for all types other than the cart and keypad - !string.IsNullOrEmpty(entity.Metadata.ItemCode) - )) + .Where(entity => entity.InvoiceTime > DateTimeOffset.UtcNow - TimeSpan.FromDays(numberOfDays)) .Aggregate(new List(), AggregateInvoiceEntitiesForStats(items)) .GroupBy(entity => entity.Date) .Select(entities => new SalesStatsItem @@ -330,26 +325,7 @@ namespace BTCPayServer.Services.Apps { return (res, e) => { - if (!string.IsNullOrEmpty(e.Metadata.ItemCode)) - { - var item = items.FirstOrDefault(p => p.Id == e.Metadata.ItemCode); - if (item == null) return res; - - var fiatPrice = e.GetPayments(true).Sum(pay => - { - var paymentMethodId = pay.GetPaymentMethodId(); - var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee; - var rate = e.GetPaymentMethod(paymentMethodId).Rate; - return rate * value; - }); - res.Add(new InvoiceStatsItem - { - ItemCode = e.Metadata.ItemCode, - FiatPrice = fiatPrice, - Date = e.InvoiceTime.Date - }); - } - else if (!string.IsNullOrEmpty(e.Metadata.PosData)) + if (!string.IsNullOrEmpty(e.Metadata.PosData)) { // flatten single items from POS data var data = JsonConvert.DeserializeObject(e.Metadata.PosData); @@ -370,6 +346,22 @@ namespace BTCPayServer.Services.Apps } } } + else + { + var fiatPrice = e.GetPayments(true).Sum(pay => + { + var paymentMethodId = pay.GetPaymentMethodId(); + var value = pay.GetCryptoPaymentData().GetValue() - pay.NetworkFee; + var rate = e.GetPaymentMethod(paymentMethodId).Rate; + return rate * value; + }); + res.Add(new InvoiceStatsItem + { + ItemCode = e.Metadata.ItemCode, + FiatPrice = fiatPrice, + Date = e.InvoiceTime.Date + }); + } return res; }; } diff --git a/BTCPayServer/Views/UIStores/Dashboard.cshtml b/BTCPayServer/Views/UIStores/Dashboard.cshtml index 2c451b70e..65c6ea516 100644 --- a/BTCPayServer/Views/UIStores/Dashboard.cshtml +++ b/BTCPayServer/Views/UIStores/Dashboard.cshtml @@ -1,14 +1,17 @@ @using BTCPayServer.Components.StoreLightningBalance +@using BTCPayServer.Components.StoreLightningServices @using BTCPayServer.Components.StoreNumbers +@using BTCPayServer.Components.StoreRecentInvoices +@using BTCPayServer.Components.StoreRecentTransactions @using BTCPayServer.Components.StoreWalletBalance @using BTCPayServer.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer.Components.AppSales +@using BTCPayServer.Components.AppTopItems @model StoreDashboardViewModel; -@inject BTCPayNetworkProvider networkProvider @{ ViewData.SetActivePage(StoreNavPages.Dashboard, Model.StoreName, Model.StoreId); - var defaultCryptoCode = networkProvider.DefaultNetwork.CryptoCode; var store = ViewContext.HttpContext.GetStoreData(); } @@ -101,7 +104,7 @@
Set up a Lightning node
- +
Set up a wallet
@@ -111,21 +114,21 @@
} - + @if (Model.LightningEnabled) { - - + + } @if (Model.WalletEnabled) { - + } - + @foreach (var app in Model.Apps) { - - + + } } @@ -142,7 +145,7 @@ else @if (!Model.WalletEnabled) { -
+