Store centric UI: Part 3 (#3224)

* Set store context in cookie

* Fix page id usages in view

* Move Pay Button to nav

* Move integrations to plugins nav

* Store switch links to wallet if present

* Test fixes

* Nav fixes

* Fix altcoin view

* Main nav updates

* Wallet setttings nav update

* Move storeId cookie fallback to cookie auth handler

* View fixes

* Test fixes

* Fix profile check

* Rename integrations nav extension point to store-integrations-nav-list

* Allow strings for Active page/category for plugins

* Make invoice list filter based on store context

* Do not set context if we are running authorizer through tag helper

* Fix test and unfiltered invoices

* Add permission helper for wallet links

* Add sanity checks for payment requests and invoices

* Store context in home controller

* Fix PayjoinViaUI test

* Store context for notifications

* Minor UI improvements

* Store context for userstores and vault controller

* Bring back integrations page

* Rename notifications nav pages file

* Fix user stores controller policies

* Controller policy fixes from code review

* CookieAuthHandler: Simplify CanViewInvoices case

* Revert "Controller policy fixes from code review"

This reverts commit 97e8b8379c.

* Simplify LayoutSimple

* Fix CanViewInvoices condition

Co-authored-by: Kukks <evilkukka@gmail.com>
This commit is contained in:
d11n 2021-12-31 08:36:38 +01:00 committed by GitHub
parent db1a124ffb
commit e2d0b7c5f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 625 additions and 512 deletions

View file

@ -13,13 +13,18 @@ namespace BTCPayServer.Abstractions.Extensions
public static void SetActivePage<T>(this ViewDataDictionary viewData, T activePage, string title = null, string activeId = null)
where T : IConvertible
{
SetActivePage(viewData, activePage.ToString(), activePage.GetType().Name,title, activeId );
}
public static void SetActivePage(this ViewDataDictionary viewData, string activePage, string category, string title = null, string activeId = null)
{
// Page Title
viewData["Title"] = title ?? activePage.ToString();
viewData["Title"] = title ?? activePage;
// Navigation
viewData[ACTIVE_PAGE_KEY] = activePage;
viewData[ACTIVE_ID_KEY] = activeId;
SetActiveCategory(viewData, activePage.GetType());
SetActiveCategory(viewData, category);
}
public static void SetActiveCategory<T>(this ViewDataDictionary viewData, T activeCategory)
@ -27,30 +32,43 @@ namespace BTCPayServer.Abstractions.Extensions
viewData[ACTIVE_CATEGORY_KEY] = activeCategory;
}
public static void SetActiveCategory(this ViewDataDictionary viewData, string activeCategory)
{
viewData[ACTIVE_CATEGORY_KEY] = activeCategory;
}
public static string IsActiveCategory<T>(this ViewDataDictionary viewData, T category, object id = null)
{
return IsActiveCategory(viewData, category.ToString(), id);
}
public static string IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null)
{
if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY))
{
return null;
}
var activeId = viewData[ACTIVE_ID_KEY];
var activeCategory = (T)viewData[ACTIVE_CATEGORY_KEY];
var categoryMatch = category.Equals(activeCategory);
var activeCategory = viewData[ACTIVE_CATEGORY_KEY]?.ToString();
var categoryMatch = category.Equals(activeCategory, StringComparison.InvariantCultureIgnoreCase);
var idMatch = id == null || activeId == null || id.Equals(activeId);
return categoryMatch && idMatch ? "active" : null;
}
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null)
where T : IConvertible
{
return IsActivePage(viewData, page.ToString(), page.GetType().Name, id );
}
public static string IsActivePage(this ViewDataDictionary viewData, string page,string category, object id = null)
{
if (!viewData.ContainsKey(ACTIVE_PAGE_KEY))
{
return null;
}
var activeId = viewData[ACTIVE_ID_KEY];
var activePage = (T)viewData[ACTIVE_PAGE_KEY];
var activeCategory = viewData[ACTIVE_CATEGORY_KEY];
var categoryAndPageMatch = activeCategory.Equals(activePage.GetType()) && page.Equals(activePage);
var activePage = viewData[ACTIVE_PAGE_KEY]?.ToString();
var activeCategory = viewData[ACTIVE_CATEGORY_KEY]?.ToString();
var categoryAndPageMatch = ( category == null || activeCategory.Equals(category, StringComparison.InvariantCultureIgnoreCase )) && page.Equals(activePage, StringComparison.InvariantCultureIgnoreCase);
var idMatch = id == null || activeId == null || id.Equals(activeId);
return categoryAndPageMatch && idMatch ? "active" : null;
}

View file

@ -404,8 +404,6 @@ namespace BTCPayServer.Tests
Assert.Contains("2.20000000 ₿", s.Driver.PageSource);
if (rateSelection == "RateThenText")
Assert.Contains("1.10000000 ₿", s.Driver.PageSource);
s.GoToHome();
s.GoToInvoices();
s.GoToInvoice(invoice.Id);
s.Driver.FindElement(By.Id("refundlink")).Click();
Assert.Contains("pull-payments", s.Driver.Url);
@ -423,19 +421,19 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC");
//check that there is no dropdown since only one payment method is set
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
var invoiceId = s.CreateInvoice(storeId, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.GoToHome();
s.GoToStore(store.storeId);
s.GoToStore(storeId);
s.AddDerivationScheme("LTC");
s.AddLightningNode("BTC", LightningConnectionType.CLightning);
//there should be three now
invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
invoiceId = s.CreateInvoice(storeId, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("BTC", currencyDropdownButton.Text);

View file

@ -28,17 +28,17 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC");
s.GoToStore(store.storeId, StoreNavPages.CheckoutAppearance);
s.GoToStore(storeId, StoreNavPages.CheckoutAppearance);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).Click();
var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
var emailAlreadyThereInvoiceId = s.CreateInvoice(storeId, 100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
var invoiceId = s.CreateInvoice(store.storeName);
s.CreateInvoice(storeId);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
@ -79,11 +79,11 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC");
// Now create an invoice that requires a refund email
var invoice = s.CreateInvoice(store.storeName, 100, "USD", "", null, true);
var invoice = s.CreateInvoice(storeId, 100, "USD", "", null, true);
s.GoToInvoiceCheckout(invoice);
var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
@ -106,7 +106,7 @@ namespace BTCPayServer.Tests
s.GoToHome();
// Now create an invoice that doesn't require a refund email
s.CreateInvoice(store.storeName, 100, "USD", "", null, false);
s.CreateInvoice(storeId, 100, "USD", "", null, false);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
@ -119,7 +119,7 @@ namespace BTCPayServer.Tests
s.GoToHome();
// Now create an invoice that requires refund email but already has one set, email input shouldn't show up
s.CreateInvoice(store.storeName, 100, "USD", "a@g.com", null, true);
s.CreateInvoice(storeId, 100, "USD", "a@g.com", null, true);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
@ -139,10 +139,10 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(store.storeName);
var invoiceId = s.CreateInvoice(storeId);
s.GoToInvoiceCheckout(invoiceId);
Assert.True(s.Driver.FindElement(By.Id("DefaultLang")).FindElements(By.TagName("option")).Count > 1);
var payWithTextEnglish = s.Driver.FindElement(By.Id("pay-with-text")).Text;
@ -171,11 +171,11 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser(true);
var store = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.AddLightningNode();
s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(store.storeName, defaultPaymentMethod: "BTC_LightningLike");
var invoiceId = s.CreateInvoice(storeId, defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId);
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
@ -193,7 +193,7 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.AddLightningNode();
s.GoToStore(storeId);
s.Driver.FindElement(By.Id("Modify-LightningBTC")).Click();
@ -201,7 +201,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text);
var invoiceId = s.CreateInvoice(storeName, 10, "USD", "a@g.com");
var invoiceId = s.CreateInvoice(storeId, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
}
@ -215,10 +215,10 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.GoToStore(store.storeId);
(_, string storeId) = s.CreateNewStore();
s.GoToStore(storeId);
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(store.storeId, 0.001m, "BTC", "a@x.com");
var invoiceId = s.CreateInvoice(storeId, 0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
s.Driver.Navigate()
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}"));

View file

@ -246,7 +246,7 @@ namespace BTCPayServer.Tests
await s.FundStoreWallet(senderWalletId);
await s.FundStoreWallet(receiverWalletId);
var invoiceId = s.CreateInvoice(receiver.storeName, null, "BTC");
var invoiceId = s.CreateInvoice(receiver.storeId, null, "BTC");
s.GoToInvoiceCheckout(invoiceId);
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href");
@ -295,7 +295,7 @@ namespace BTCPayServer.Tests
var receiverWalletId = new WalletId(receiver.storeId, cryptoCode);
//payjoin is enabled by default.
var invoiceId = s.CreateInvoice(receiver.storeName);
var invoiceId = s.CreateInvoice(receiver.storeId);
s.GoToInvoiceCheckout(invoiceId);
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href");
@ -310,7 +310,7 @@ namespace BTCPayServer.Tests
await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(senderWalletId);
invoiceId = s.CreateInvoice(receiver.storeName);
invoiceId = s.CreateInvoice(receiver.storeId);
s.GoToInvoiceCheckout(invoiceId);
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href");
@ -343,7 +343,7 @@ namespace BTCPayServer.Tests
StringComparison.InvariantCultureIgnoreCase));
//let's do it all again, except now the receiver has funds and is able to payjoin
invoiceId = s.CreateInvoice(receiver.storeName);
invoiceId = s.CreateInvoice(receiver.storeId);
s.GoToInvoiceCheckout(invoiceId);
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href");
@ -393,7 +393,7 @@ namespace BTCPayServer.Tests
var dto = invoice.EntityToDTO();
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
});
s.GoToInvoices();
s.GoToInvoices(receiver.storeId);
paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_details_{invoiceId}"))
.FindElement(By.ClassName("payment-value"));
Assert.False(paymentValueRowColumn.Text.Contains("payjoin",

View file

@ -332,17 +332,10 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("Password")).SendKeys(password);
Driver.FindElement(By.Id("LoginButton")).Click();
}
public void GoToApps()
{
Driver.FindElement(By.Id("StoreNav-Apps")).Click();
}
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.PaymentMethods)
{
GoToHome();
Driver.WaitForAndClick(By.Id("StoreSelectorToggle"));
Driver.WaitForAndClick(By.Id($"StoreSelectorMenuItem-{storeId}"));
GoToUrl($"/stores/{storeId}/");
if (storeNavPage != StoreNavPages.PaymentMethods)
{
@ -360,8 +353,15 @@ namespace BTCPayServer.Tests
public void GoToWalletSettings(string storeId, string cryptoCode = "BTC")
{
GoToStore(storeId);
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
try
{
GoToStore(storeId);
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
}
catch (NoSuchElementException)
{
GoToWallet(new WalletId(storeId, cryptoCode), WalletsNavPages.Settings);
}
}
public void GoToLightningSettings(string storeId, string cryptoCode = "BTC")
@ -377,10 +377,14 @@ namespace BTCPayServer.Tests
CheckForJSErrors();
}
public void GoToInvoices()
public void GoToInvoice(string id)
{
GoToHome();
Driver.FindElement(By.Id("Nav-Invoices")).Click();
GoToUrl($"/invoices/{id}/");
}
public void GoToInvoices(string storeId = null)
{
GoToUrl(storeId == null ? "/invoices/" : $"/stores/{storeId}/invoices/");
}
public void GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
@ -394,11 +398,11 @@ namespace BTCPayServer.Tests
public void GoToLogin()
{
Driver.Navigate().GoToUrl(new Uri(ServerUri, "/login"));
GoToUrl("/login");
}
public string CreateInvoice(
string storeName,
string storeId,
decimal? amount = 100,
string currency = "USD",
string refundEmail = "",
@ -407,7 +411,7 @@ namespace BTCPayServer.Tests
StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success
)
{
GoToInvoices();
GoToInvoices(storeId);
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
if (amount is decimal v)
Driver.FindElement(By.Id("Amount")).SendKeys(v.ToString(CultureInfo.InvariantCulture));
@ -415,7 +419,6 @@ namespace BTCPayServer.Tests
currencyEl.Clear();
currencyEl.SendKeys(currency);
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
if (defaultPaymentMethod is string)
new SelectElement(Driver.FindElement(By.Name("DefaultPaymentMethod"))).SelectByValue(defaultPaymentMethod);
if (requiresRefundEmail is bool)
@ -501,18 +504,5 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id($"SectionNav-{navPages}")).Click();
}
}
public void GoToInvoice(string id)
{
GoToInvoices();
foreach (var el in Driver.FindElements(By.ClassName("invoice-details-link")))
{
if (el.GetAttribute("href").Contains(id, StringComparison.OrdinalIgnoreCase))
{
el.Click();
break;
}
}
}
}
}

View file

@ -360,7 +360,7 @@ namespace BTCPayServer.Tests
s.Server.ActivateLightning();
await s.StartAsync();
var alice = s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore();
(string storeName, string storeId) = s.CreateNewStore();
var onchainHint = "Set up your wallet to receive payments at your store.";
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
@ -392,8 +392,8 @@ namespace BTCPayServer.Tests
var storeUrl = s.Driver.Url;
s.ClickOnAllSectionLinks();
s.GoToInvoices();
var invoiceId = s.CreateInvoice(storeName);
s.GoToInvoices(storeId);
var invoiceId = s.CreateInvoice(storeId);
s.FindAlertMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
@ -402,16 +402,17 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
Assert.Contains("Unarchive", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
//check that it no longer appears in list
s.GoToInvoices();
s.GoToInvoices(storeId);
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
//ok, let's unarchive and see that it shows again
s.Driver.Navigate().GoToUrl(invoiceUrl);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
s.FindAlertMessage();
Assert.DoesNotContain("Unarchive", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.GoToInvoices();
s.GoToInvoices(storeId);
Assert.Contains(invoiceId, s.Driver.PageSource);
// When logout out we should not be able to access store and invoice details
@ -614,7 +615,7 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var (_, storeId) = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.GenerateWallet("BTC", "", false, true);
var walletId = new WalletId(storeId, "BTC");
s.GoToWallet(walletId, WalletsNavPages.Receive);
@ -676,7 +677,7 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.GoToStore(storeId, StoreNavPages.Webhooks);
TestLogs.LogInformation("Let's create two webhooks");
@ -733,7 +734,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("Let's see if we can generate an event");
s.GoToStore(storeId);
s.AddDerivationScheme();
s.CreateInvoice(storeName);
s.CreateInvoice(storeId);
var request = await server.GetNextRequest();
var headers = request.Request.Headers;
var actualSig = headers["BTCPay-Sig"].First();
@ -745,14 +746,14 @@ namespace BTCPayServer.Tests
server.Done();
TestLogs.LogInformation("Let's make a failed event");
s.CreateInvoice(storeName);
s.CreateInvoice(storeId);
request = await server.GetNextRequest();
request.Response.StatusCode = 404;
server.Done();
// The delivery is done asynchronously, so small wait here
await Task.Delay(500);
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
s.GoToStore(storeId, StoreNavPages.Webhooks);
s.Driver.FindElement(By.LinkText("Modify")).Click();
var elements = s.Driver.FindElements(By.ClassName("redeliver"));
// One worked, one failed
@ -768,7 +769,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("Can we browse the json content?");
CanBrowseContent(s);
s.GoToInvoices();
s.GoToInvoices(storeId);
s.Driver.FindElement(By.LinkText("Details")).Click();
CanBrowseContent(s);
var element = s.Driver.FindElement(By.ClassName("redeliver"));
@ -798,7 +799,7 @@ namespace BTCPayServer.Tests
foreach (var isHotwallet in new[] { false, true })
{
var cryptoCode = "BTC";
var (_, storeId) = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
s.GenerateWallet(cryptoCode, "melody lizard phrase voice unique car opinion merge degree evil swift cargo", privkeys: isHotwallet);
s.GoToWalletSettings(storeId, cryptoCode);
if (isHotwallet)
@ -816,7 +817,7 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
const string cryptoCode = "BTC";
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0',
@ -865,7 +866,7 @@ namespace BTCPayServer.Tests
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
var invoiceId = s.CreateInvoice(storeName);
var invoiceId = s.CreateInvoice(storeId);
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
var address = invoice.EntityToDTO().Addresses["BTC"];
@ -878,7 +879,7 @@ namespace BTCPayServer.Tests
//lets import and save private keys
var root = mnemonic.DeriveExtKey();
invoiceId = s.CreateInvoice(storeName);
invoiceId = s.CreateInvoice(storeId);
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
address = invoice.EntityToDTO().Addresses["BTC"];
result = await s.Server.ExplorerNode.GetAddressInfoAsync(
@ -978,8 +979,8 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
(string _, string storeId) = s.CreateNewStore();
var cryptoCode = "BTC";
(_, string storeId) = s.CreateNewStore();
const string cryptoCode = "BTC";
var mnemonic = s.GenerateWallet(cryptoCode, "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
// Make sure wallet info is correct
@ -1294,7 +1295,7 @@ namespace BTCPayServer.Tests
new[] { s.Server.MerchantLightningD },
new[] { s.Server.MerchantLnd.Client });
s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore();
(_, string storeId) = s.CreateNewStore();
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
s.GoToStore(storeId);
s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning, false);
@ -1307,7 +1308,7 @@ namespace BTCPayServer.Tests
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
// Topup Invoice test
var i = s.CreateInvoice(storeName, null, cryptoCode);
var i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.Id("copy-tab")).Click();
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
@ -1339,8 +1340,8 @@ namespace BTCPayServer.Tests
});
// Standard invoice test
s.GoToHome();
i = s.CreateInvoice(storeName, 0.0000001m, cryptoCode);
s.GoToStore(storeId);
i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
// BOLT11 is also available for standard invoices
@ -1382,12 +1383,12 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
i = s.CreateInvoice(storeName, 0.000001m, cryptoCode);
i = s.CreateInvoice(storeId, 0.000001m, cryptoCode);
s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.GoToHome();
i = s.CreateInvoice(storeName, null, cryptoCode);
s.GoToStore(storeId);
i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
@ -1409,9 +1410,9 @@ namespace BTCPayServer.Tests
//even though we set DisableBolt11PaymentMethod to true, logic when saving it turns it back off as otherwise no lightning option is available at all!
Assert.False(s.Driver.FindElement(By.Id("DisableBolt11PaymentMethod")).Selected);
// Invoice creation should fail, because it is a standard invoice with amount, but DisableBolt11PaymentMethod = true and LNURLStandardInvoiceEnabled = false
s.CreateInvoice(storeName, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Success);
s.CreateInvoice(storeId, 0.0000001m, cryptoCode,"",null, expectedSeverity: StatusMessageModel.StatusSeverity.Success);
i = s.CreateInvoice(storeName, null, cryptoCode);
i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.Driver.FindElement(By.Id("copy-tab")).Click();
@ -1427,7 +1428,7 @@ namespace BTCPayServer.Tests
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text);
var invForPP = s.CreateInvoice(newStore.storeName, 0.0000001m, cryptoCode);
var invForPP = s.CreateInvoice(newStore.storeId, 0.0000001m, cryptoCode);
s.GoToInvoiceCheckout(invForPP);
s.Driver.FindElement(By.Id("copy-tab")).Click();
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
@ -1486,25 +1487,19 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true);
//ln address tests
s.CreateNewStore();
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
//ensure ln address is not available as Lightning is not enable
s.Driver.FindElement(By.Id("lightning-address-option"))
.FindElement(By.LinkText("You need to setup Lightning first"));
s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
s.GoToStore(s.StoreId);
s.AddLightningNode("BTC", LightningConnectionType.LndREST, false);
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
//ensure ln address is not available as lnurl is not configured
s.Driver.FindElement(By.Id("lightning-address-option"))
.FindElement(By.LinkText("You need LNURL configured first"));
s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
s.GoToLightningSettings(s.StoreId, cryptoCode);
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
s.Driver.FindElement(By.Id("lightning-address-option"))
.FindElement(By.Id("lightning-address-setup-link")).Click();
s.Driver.FindElement(By.Id("StoreNav-LightningAddress")).Click();
s.Driver.ToggleCollapse("AddAddress");
var lnaddress1 = Guid.NewGuid().ToString();

View file

@ -339,7 +339,7 @@ namespace BTCPayServer.Tests
tester.PayTester.MockRates = false;
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
List<decimal> rates = new List<decimal>();
rates.Add(await CreateInvoice(tester, user, "coingecko"));

View file

@ -794,11 +794,11 @@ namespace BTCPayServer.Tests
{
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess();
await acc.GrantAccessAsync();
acc.RegisterDerivationScheme("BTC");
// First we try payment with a merchant having only BTC
var invoice = acc.BitPay.CreateInvoice(
new Invoice()
var invoice = await acc.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 500,
Currency = "USD",
@ -811,21 +811,22 @@ namespace BTCPayServer.Tests
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Satoshis(10);
cashCow.SendToAddress(invoiceAddress, firstPayment);
await cashCow.SendToAddressAsync(invoiceAddress, firstPayment);
TestUtils.Eventually(() =>
{
invoice = acc.BitPay.GetInvoice(invoice.Id);
Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid);
});
AssertSearchInvoice(acc, true, invoice.Id, null);
AssertSearchInvoice(acc, true, invoice.Id, null, acc.StoreId);
AssertSearchInvoice(acc, true, invoice.Id, $"storeid:{acc.StoreId}");
AssertSearchInvoice(acc, false, invoice.Id, $"storeid:blah");
AssertSearchInvoice(acc, false, invoice.Id, "storeid:doesnotexist");
AssertSearchInvoice(acc, true, invoice.Id, $"{invoice.Id}");
AssertSearchInvoice(acc, true, invoice.Id, $"exceptionstatus:paidPartial");
AssertSearchInvoice(acc, false, invoice.Id, $"exceptionstatus:paidOver");
AssertSearchInvoice(acc, true, invoice.Id, $"unusual:true");
AssertSearchInvoice(acc, false, invoice.Id, $"unusual:false");
AssertSearchInvoice(acc, true, invoice.Id, "exceptionstatus:paidPartial");
AssertSearchInvoice(acc, false, invoice.Id, "exceptionstatus:paidOver");
AssertSearchInvoice(acc, true, invoice.Id, "unusual:true");
AssertSearchInvoice(acc, false, invoice.Id, "unusual:false");
var time = invoice.InvoiceTime;
AssertSearchInvoice(acc, true, invoice.Id, $"startdate:{time.ToString("yyyy-MM-dd HH:mm:ss")}");
@ -929,11 +930,11 @@ namespace BTCPayServer.Tests
}
}
private void AssertSearchInvoice(TestAccount acc, bool expected, string invoiceId, string filter)
private void AssertSearchInvoice(TestAccount acc, bool expected, string invoiceId, string filter, string storeId = null)
{
var result =
(InvoicesModel)((ViewResult)acc.GetController<InvoiceController>()
.ListInvoices(new InvoicesModel { SearchTerm = filter }).Result).Model;
.ListInvoices(new InvoicesModel { SearchTerm = filter, StoreId = storeId }).Result).Model;
Assert.Equal(expected, result.Invoices.Any(i => i.InvoiceId == invoiceId));
}

View file

@ -23,59 +23,7 @@
<div class="accordion px-3 px-lg-4">
@if (SignInManager.IsSignedIn(User))
{
@if (Model.Store == null)
{
<div class="accordion-item">
<header class="accordion-header" id="Nav-Payments-Header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Payments" aria-expanded="true" aria-controls="Nav-Payments">
Payments
<vc:icon symbol="caret-down"/>
</button>
</header>
<div id="Nav-Payments" class="accordion-collapse collapse show" aria-labelledby="Nav-Payments-Header">
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item">
<a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(InvoiceNavPages))" id="Nav-Invoices">
<vc:icon symbol="invoice"/>
<span>Invoices</span>
</a>
</li>
@* FIXME: The wallets item is in here only for the tests *@
<li class="nav-item">
<a asp-area="" asp-controller="Wallets" asp-action="ListWallets" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(WalletsNavPages))" id="Nav-Wallets">
<vc:icon symbol="wallet-onchain"/>
<span>Wallets</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item" permission="@Policies.CanModifyServerSettings">
<header class="accordion-header" id="Nav-Plugins-Header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Plugins" aria-expanded="true" aria-controls="Nav-Plugins">
Plugins
<vc:icon symbol="caret-down"/>
</button>
</header>
<div id="Nav-Plugins" class="accordion-collapse collapse show" aria-labelledby="Nav-Plugins-Header">
<div class="accordion-body">
<ul class="navbar-nav">
<vc:ui-extension-point location="header-nav" model="@Model"/>
@* TODO: Limit this to admins *@
<li class="nav-item">
<a asp-area="" asp-controller="Server" asp-action="ListPlugins" class="nav-link js-scroll-trigger @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-AddPlugin">
<vc:icon symbol="new"/>
<span>Add Plugin</span>
</a>
</li>
</ul>
</div>
</div>
</div>
}
else
@if (Model.Store != null)
{
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<div class="accordion-body">
@ -84,16 +32,16 @@
{
var isSetUp = !string.IsNullOrWhiteSpace(scheme.Value);
<li class="nav-item">
@if (isSetUp)
@if (isSetUp && scheme.WalletSupported)
{
<a asp-area="" asp-controller="Stores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link" id="@($"StoreNav-Modify{scheme.Crypto}")">
<a asp-area="" asp-controller="Wallets" asp-action="WalletTransactions" asp-route-walletId="@scheme.WalletId"class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(WalletsNavPages), scheme.WalletId.ToString()) @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
</a>
}
else
{
<a asp-area="" asp-controller="Stores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link" id="@($"StoreNav-Modify{scheme.Crypto}")">
<a asp-area="" asp-controller="Stores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Modify{scheme.Crypto}")">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
</a>
@ -106,14 +54,14 @@
<li class="nav-item">
@if (isSetUp)
{
<a asp-area="" asp-controller="Stores" asp-action="LightningSettings" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
<a asp-area="" asp-controller="Stores" asp-action="LightningSettings" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.LightningSettings)" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span>
</a>
}
else
{
<a asp-area="" asp-controller="Stores" asp-action="SetupLightningNode" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
<a asp-area="" asp-controller="Stores" asp-action="SetupLightningNode" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.LightningSettings)" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
<span class="me-2 btcpay-status btcpay-status--disabled"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span>
</a>
@ -134,19 +82,6 @@
<div id="Nav-Payments" class="accordion-collapse collapse show" aria-labelledby="Nav-Payments-Header">
<div class="accordion-body">
<ul class="navbar-nav">
@foreach (var scheme in Model.DerivationSchemes.OrderBy(scheme => scheme.Collapsed))
{
var isSetUp = !string.IsNullOrWhiteSpace(scheme.Value);
if (isSetUp && scheme.WalletSupported)
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="Wallets" asp-action="WalletTransactions" asp-route-walletId="@scheme.WalletId" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(WalletsNavPages), scheme.Crypto)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
<vc:icon symbol="wallet-onchain"/>
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
</a>
</li>
}
}
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="Invoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(InvoiceNavPages))" id="StoreNav-Invoices">
<vc:icon symbol="invoice"/>
@ -171,6 +106,12 @@
<span>Payouts</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" id="StoreNav-@(nameof(StoreNavPages.PayButton))">
<vc:icon symbol="payment-2"/>
<span>Pay Button</span>
</a>
</li>
</ul>
</div>
</div>
@ -204,6 +145,28 @@
</div>
</div>
</div>
<div class="accordion-item">
<header class="accordion-header" id="Nav-Plugins-Header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Plugins" aria-expanded="true" aria-controls="Nav-Plugins">
Plugins
<vc:icon symbol="caret-down"/>
</button>
</header>
<div id="Nav-Plugins" class="accordion-collapse collapse show" aria-labelledby="Nav-Plugins-Header">
<div class="accordion-body">
<ul class="navbar-nav">
<vc:ui-extension-point location="header-nav" model="@Model"/>
<vc:ui-extension-point location="store-integrations-nav" model="@Model" />
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
<a asp-area="" asp-controller="Server" asp-action="ListPlugins" class="nav-link js-scroll-trigger @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-AddPlugin">
<vc:icon symbol="new"/>
<span>Add Plugin</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<header class="accordion-header" id="Nav-Manage-Header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Manage" aria-expanded="true" aria-controls="Nav-Manage">
@ -215,7 +178,7 @@
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item">
<a asp-area="" asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.PaymentMethods) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.GeneralSettings) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.PayButton) @ViewData.IsActivePage(StoreNavPages.Integrations) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-Invoices">
<a asp-area="" asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.PaymentMethods) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.GeneralSettings) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
<vc:icon symbol="settings"/>
<span>Store Settings</span>
</a>
@ -256,7 +219,7 @@
</div>
<ul id="mainNavSettings" class="navbar-nav border-top p-3 px-lg-4">
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
<a asp-area="" asp-controller="Server" asp-action="ListUsers" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(ServerNavPages))" id="Nav-ServerSettings">
<a asp-area="" asp-controller="Server" asp-action="ListUsers" class="nav-link js-scroll-trigger @ViewData.IsActivePage(ServerNavPages.Users) @ViewData.IsActivePage(ServerNavPages.Emails) @ViewData.IsActivePage(ServerNavPages.Policies) @ViewData.IsActivePage(ServerNavPages.Services) @ViewData.IsActivePage(ServerNavPages.Theme) @ViewData.IsActivePage(ServerNavPages.Maintenance) @ViewData.IsActivePage(ServerNavPages.Logs) @ViewData.IsActivePage(ServerNavPages.Files)" id="Nav-ServerSettings">
<vc:icon symbol="server-settings"/>
<span>Server Settings</span>
</a>
@ -264,7 +227,7 @@
<li class="nav-item">
<a asp-area="" asp-controller="Manage" asp-action="Index" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(ManageNavPages))" id="Nav-Account">
<vc:icon symbol="account"/>
<span>Account</span>
<span class="text-truncate" style="max-width:195px">@User.Identity.Name</span>
</a>
</li>
@if (!theme.CustomTheme)

View file

@ -7,7 +7,16 @@
<ul id="StoreSelectorMenu" class="dropdown-menu" aria-labelledby="StoreSelectorToggle">
@foreach (var option in Model.Options)
{
<li><a asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@option.Text</a></li>
<li>
@if (option.WalletId != null)
{
<a asp-controller="Wallets" asp-action="WalletTransactions" asp-route-walletId="@option.WalletId" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@option.Text</a>
}
else
{
<a asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@option.Text</a>
}
</li>
}
<li><hr class="dropdown-divider"></li>
<li><a asp-controller="UserStores" asp-action="CreateStore" class="dropdown-item" id="StoreSelectorMenuItem-Create">Create Store</a></li>

View file

@ -5,20 +5,23 @@ using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using NBitcoin.Secp256k1;
namespace BTCPayServer.Components.StoreSelector
{
public class StoreSelector : ViewComponent
{
private const string RootName = "Global";
private readonly StoreRepository _storeRepo;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly UserManager<ApplicationUser> _userManager;
public StoreSelector(StoreRepository storeRepo, UserManager<ApplicationUser> userManager)
public StoreSelector(
StoreRepository storeRepo,
BTCPayNetworkProvider networkProvider,
UserManager<ApplicationUser> userManager)
{
_storeRepo = storeRepo;
_userManager = userManager;
_networkProvider = networkProvider;
}
public async Task<IViewComponentResult> InvokeAsync()
@ -27,11 +30,21 @@ namespace BTCPayServer.Components.StoreSelector
var stores = await _storeRepo.GetStoresByUserId(userId);
var currentStore = ViewContext.HttpContext.GetStoreData();
var options = stores
.Select(store => new SelectListItem
.Select(store =>
{
Text = store.StoreName,
Value = store.Id,
Selected = store.Id == currentStore?.Id
var cryptoCode = store
.GetSupportedPaymentMethods(_networkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault()?
.Network.CryptoCode;
var walletId = cryptoCode != null ? new WalletId(store.Id, cryptoCode) : null;
return new StoreSelectorOption
{
Text = store.StoreName,
Value = store.Id,
Selected = store.Id == currentStore?.Id,
WalletId = walletId
};
})
.ToList();
@ -39,7 +52,7 @@ namespace BTCPayServer.Components.StoreSelector
{
Options = options,
CurrentStoreId = currentStore?.Id,
CurrentDisplayName = currentStore?.StoreName ?? RootName
CurrentDisplayName = currentStore?.StoreName
};
return View(vm);

View file

@ -1,12 +1,19 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Components.StoreSelector
{
public class StoreSelectorViewModel
{
public List<SelectListItem> Options { get; set; }
public List<StoreSelectorOption> Options { get; set; }
public string CurrentStoreId { get; set; }
public string CurrentDisplayName { get; set; }
}
public class StoreSelectorOption
{
public bool Selected { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public WalletId WalletId { get; set; }
}
}

View file

@ -16,6 +16,7 @@ using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Security;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Stores;
using ExchangeSharp;
using Google.Apis.Auth.OAuth2;
using Microsoft.AspNetCore.Authorization;
@ -26,6 +27,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using NBitcoin;
using NBitcoin.Payment;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -34,44 +36,59 @@ namespace BTCPayServer.Controllers
public class HomeController : Controller
{
private readonly ISettingsRepository _settingsRepository;
private readonly StoreRepository _storeRepository;
private readonly IFileProvider _fileProvider;
public IHttpClientFactory HttpClientFactory { get; }
private IHttpClientFactory HttpClientFactory { get; }
private SignInManager<ApplicationUser> SignInManager { get; }
public LanguageService LanguageService { get; }
SignInManager<ApplicationUser> SignInManager { get; }
public HomeController(IHttpClientFactory httpClientFactory,
ISettingsRepository settingsRepository,
IWebHostEnvironment webHostEnvironment,
LanguageService languageService,
StoreRepository storeRepository,
SignInManager<ApplicationUser> signInManager)
{
_settingsRepository = settingsRepository;
HttpClientFactory = httpClientFactory;
LanguageService = languageService;
_storeRepository = storeRepository;
_fileProvider = webHostEnvironment.WebRootFileProvider;
SignInManager = signInManager;
}
[Route("")]
[DomainMappingConstraint()]
[DomainMappingConstraint]
public async Task<IActionResult> Index()
{
if ((await _settingsRepository.GetTheme()).FirstRun)
{
return RedirectToAction(nameof(AccountController.Register), "Account");
}
if (SignInManager.IsSignedIn(User))
{
var storeId = HttpContext.GetUserPrefsCookie()?.CurrentStoreId;
if (storeId != null)
{
var userId = SignInManager.UserManager.GetUserId(HttpContext.User);
var store = await _storeRepository.FindStore(storeId, userId);
if (store != null)
{
HttpContext.SetStoreData(store);
}
}
return View("Home");
else
return Challenge();
}
return Challenge();
}
[Route("misc/lang")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
public IActionResult Languages()
{
return Json(LanguageService.GetLanguages(), new JsonSerializerSettings() { Formatting = Formatting.Indented });
return Json(LanguageService.GetLanguages(), new JsonSerializerSettings { Formatting = Formatting.Indented });
}
@ -79,7 +96,7 @@ namespace BTCPayServer.Controllers
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
public IActionResult Permissions()
{
return Json(Client.Models.PermissionMetadata.PermissionNodes, new JsonSerializerSettings() { Formatting = Formatting.Indented });
return Json(Client.Models.PermissionMetadata.PermissionNodes, new JsonSerializerSettings { Formatting = Formatting.Indented });
}
[Route("swagger/v1/swagger.json")]
@ -108,7 +125,7 @@ namespace BTCPayServer.Controllers
}
[Route("recovery-seed-backup")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public IActionResult RecoverySeedBackup(RecoverySeedBackupViewModel vm)
{
return View("RecoverySeedBackup", vm);

View file

@ -740,29 +740,22 @@ namespace BTCPayServer.Controllers
[HttpGet("/stores/{storeId}/invoices")]
[HttpGet("invoices")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewInvoices)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(InvoicesModel? model = null, string? storeId = null)
public async Task<IActionResult> ListInvoices(InvoicesModel? model = null)
{
model = this.ParseListQuery(model ?? new InvoicesModel());
var fs = new SearchString(model.SearchTerm);
var storeIds = storeId == null
var store = model.StoreId == null || fs.ContainsFilter("storeid") ? null : HttpContext.GetStoreData();
var storeIds = store == null
? fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List<string>().ToArray()
: new []{ storeId };
: new []{ store.Id };
model.StoreIds = storeIds;
if (storeId != null)
{
var store = await _StoreRepository.FindStore(storeId, GetUserId());
if (store == null)
return NotFound();
HttpContext.SetStoreData(store);
model.StoreId = store.Id;
}
InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
invoiceQuery.StoreId = storeIds;
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
invoiceQuery.Take = model.Count;
invoiceQuery.Skip = model.Skip;

View file

@ -2,6 +2,7 @@ using System;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Fido2;
using BTCPayServer.Models;
@ -21,7 +22,7 @@ using Microsoft.Extensions.Logging;
namespace BTCPayServer.Controllers
{
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewProfile)]
[Route("[controller]/[action]")]
public partial class ManageController : Controller
{

View file

@ -4,6 +4,7 @@ using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Filters;
using BTCPayServer.Models.NotificationViewModels;
@ -18,7 +19,7 @@ using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
[BitpayAPIConstraint(false)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewNotificationsForUser)]
[Route("[controller]/[action]")]
public class NotificationsController : Controller
{
@ -46,8 +47,7 @@ namespace BTCPayServer.Controllers
{
return ViewComponent("NotificationsDropdown");
}
[HttpGet]
public async Task<IActionResult> SubscribeUpdates(CancellationToken cancellationToken)
{
@ -128,6 +128,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanManageNotificationsForUser)]
public async Task<IActionResult> FlipRead(string id)
{
if (ValidUserClaim(out var userId))
@ -161,9 +162,9 @@ namespace BTCPayServer.Controllers
return NotFound();
}
[HttpPost]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanManageNotificationsForUser)]
public async Task<IActionResult> MassAction(string command, string[] selectedItems)
{
if (!ValidUserClaim(out var userId))
@ -209,6 +210,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanManageNotificationsForUser)]
public async Task<IActionResult> MarkAllAsSeen(string returnUrl)
{
if (!ValidUserClaim(out var userId))

View file

@ -18,7 +18,7 @@ namespace BTCPayServer.Controllers
{
return View("Integrations", new IntegrationsViewModel());
}
private async Task<Data.WebhookDeliveryData?> LastDeliveryForWebhook(string webhookId)
{
return (await _Repo.GetWebhookDeliveries(CurrentStore.Id, webhookId, 1)).ToList().FirstOrDefault();

View file

@ -1,6 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
@ -14,40 +15,36 @@ using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Controllers
{
[Route("stores")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
public partial class UserStoresController : Controller
public class UserStoresController : Controller
{
private readonly StoreRepository _Repo;
private readonly BTCPayNetworkProvider _NetworkProvider;
private readonly UserManager<ApplicationUser> _UserManager;
private readonly StoreRepository _repo;
private readonly UserManager<ApplicationUser> _userManager;
public UserStoresController(
UserManager<ApplicationUser> userManager,
BTCPayNetworkProvider networkProvider,
StoreRepository storeRepository)
{
_Repo = storeRepository;
_NetworkProvider = networkProvider;
_UserManager = userManager;
_repo = storeRepository;
_userManager = userManager;
}
[HttpGet]
[Route("create")]
[HttpGet("create")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettingsUnscoped)]
public IActionResult CreateStore()
{
return View();
}
[HttpPost]
[Route("create")]
[HttpPost("create")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettingsUnscoped)]
public async Task<IActionResult> CreateStore(CreateStoreViewModel vm)
{
if (!ModelState.IsValid)
{
return View(vm);
}
var store = await _Repo.CreateStore(GetUserId(), vm.Name);
var store = await _repo.CreateStore(GetUserId(), vm.Name);
CreatedStoreId = store.Id;
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
return RedirectToAction(nameof(StoresController.PaymentMethods), "Stores", new
@ -62,6 +59,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{storeId}/me/delete")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public IActionResult DeleteStore(string storeId)
{
var store = HttpContext.GetStoreData();
@ -71,25 +69,27 @@ namespace BTCPayServer.Controllers
}
[HttpPost("{storeId}/me/delete")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public async Task<IActionResult> DeleteStorePost(string storeId)
{
var userId = GetUserId();
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
await _Repo.RemoveStore(storeId, userId);
await _repo.RemoveStore(storeId, userId);
TempData[WellKnownTempData.SuccessMessage] = "Store removed successfully";
return RedirectToAction(nameof(ListStores));
}
[HttpGet]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewStoreSettings)]
public async Task<IActionResult> ListStores(
string sortOrder = null,
string sortOrderColumn = null
)
{
StoresViewModel result = new StoresViewModel();
var stores = await _Repo.GetStoresByUserId(GetUserId());
var stores = await _repo.GetStoresByUserId(GetUserId());
if (sortOrder != null && sortOrderColumn != null)
{
stores = stores.OrderByDescending(store =>
@ -134,9 +134,6 @@ namespace BTCPayServer.Controllers
return View(result);
}
private string GetUserId()
{
return _UserManager.GetUserId(User);
}
private string GetUserId() => _userManager.GetUserId(User);
}
}

View file

@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Hwi;
@ -18,6 +19,7 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Controllers
{
[Route("vault")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public class VaultController : Controller
{
private readonly IAuthorizationService _authorizationService;

View file

@ -34,6 +34,7 @@ using NBitcoin.Payment;
using NBitpayClient;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
@ -409,6 +410,32 @@ namespace BTCPayServer
result = default;
return false;
}
public static UserPrefsCookie GetUserPrefsCookie(this HttpContext ctx)
{
var prefCookie = new UserPrefsCookie();
ctx.Request.Cookies.TryGetValue(nameof(UserPrefsCookie), out var strPrefCookie);
if (!string.IsNullOrEmpty(strPrefCookie))
{
try
{
prefCookie = JsonConvert.DeserializeObject<UserPrefsCookie>(strPrefCookie);
}
catch { /* ignore cookie deserialization failures */ }
}
return prefCookie;
}
private static void SetCurrentStoreId(this HttpContext ctx, string storeId)
{
var prefCookie = ctx.GetUserPrefsCookie();
if (prefCookie.CurrentStoreId != storeId)
{
prefCookie.CurrentStoreId = storeId;
ctx.Response.Cookies.Append(nameof(UserPrefsCookie), JsonConvert.SerializeObject(prefCookie));
}
}
public static StoreData GetStoreData(this HttpContext ctx)
{
@ -418,12 +445,15 @@ namespace BTCPayServer
public static void SetStoreData(this HttpContext ctx, StoreData storeData)
{
ctx.Items["BTCPAY.STOREDATA"] = storeData;
SetCurrentStoreId(ctx, storeData.Id);
}
public static StoreData[] GetStoresData(this HttpContext ctx)
{
return ctx.Items.TryGet("BTCPAY.STORESDATA") as StoreData[];
}
public static void SetStoresData(this HttpContext ctx, StoreData[] storeData)
{
ctx.Items["BTCPAY.STORESDATA"] = storeData;

View file

@ -10,7 +10,7 @@ using Newtonsoft.Json;
namespace BTCPayServer
{
// Classes here remember users preferences on certain pages and store them in unified blob cookie "UserPreferCookie"
// Classes here remember users preferences on certain pages and store them in unified blob cookie "UserPrefsCookie"
public static class ControllerBaseExtension
{
public static T ParseListQuery<T>(this ControllerBase ctrl, T model) where T : BasePagingViewModel
@ -34,7 +34,7 @@ namespace BTCPayServer
private static T ProcessParse<T>(ControllerBase ctrl, T model, PropertyInfo prop) where T : BasePagingViewModel
{
var prefCookie = parsePrefCookie(ctrl);
var prefCookie = ctrl.HttpContext.GetUserPrefsCookie();
// If the user enter an empty searchTerm, then the variable will be null and not empty string
// but we want searchTerm to be null only if the user is browsing the page via some link
@ -46,7 +46,7 @@ namespace BTCPayServer
if (searchTerm is null)
{
var section = prop.GetValue(prefCookie) as ListQueryDataHolder;
if (section != null && !String.IsNullOrEmpty(section.SearchTerm))
if (section != null && !string.IsNullOrEmpty(section.SearchTerm))
{
model.SearchTerm = section.SearchTerm;
model.TimezoneOffset = section.TimezoneOffset ?? 0;
@ -60,45 +60,5 @@ namespace BTCPayServer
return model;
}
private static UserPrefsCookie parsePrefCookie(ControllerBase ctrl)
{
var prefCookie = new UserPrefsCookie();
ctrl.Request.Cookies.TryGetValue(nameof(UserPrefsCookie), out var strPrefCookie);
if (!String.IsNullOrEmpty(strPrefCookie))
{
try
{
prefCookie = JsonConvert.DeserializeObject<UserPrefsCookie>(strPrefCookie);
}
catch { /* ignore cookie deserialization failures */ }
}
return prefCookie;
}
class UserPrefsCookie
{
public ListQueryDataHolder InvoicesQuery { get; set; }
public ListQueryDataHolder PaymentRequestsQuery { get; set; }
public ListQueryDataHolder UsersQuery { get; set; }
public ListQueryDataHolder PayoutsQuery { get; set; }
public ListQueryDataHolder PullPaymentsQuery { get; set; }
}
class ListQueryDataHolder
{
public ListQueryDataHolder() { }
public ListQueryDataHolder(string searchTerm, int? timezoneOffset)
{
SearchTerm = searchTerm;
TimezoneOffset = timezoneOffset;
}
public int? TimezoneOffset { get; set; }
public string SearchTerm { get; set; }
}
}
}

View file

@ -0,0 +1,26 @@
namespace BTCPayServer
{
public class UserPrefsCookie
{
public ListQueryDataHolder InvoicesQuery { get; set; }
public ListQueryDataHolder PaymentRequestsQuery { get; set; }
public ListQueryDataHolder UsersQuery { get; set; }
public ListQueryDataHolder PayoutsQuery { get; set; }
public ListQueryDataHolder PullPaymentsQuery { get; set; }
public string CurrentStoreId { get; set; }
}
public class ListQueryDataHolder
{
public ListQueryDataHolder() { }
public ListQueryDataHolder(string searchTerm, int? timezoneOffset)
{
SearchTerm = searchTerm;
TimezoneOffset = timezoneOffset;
}
public int? TimezoneOffset { get; set; }
public string SearchTerm { get; set; }
}
}

View file

@ -329,6 +329,8 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LightningLikePaymentHandler>());
services.AddSingleton<LNURLPayPaymentHandler>();
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<LNURLPayPaymentHandler>());
services.AddSingleton<IUIExtension>(new UIExtension("LNURL/LightningAddressNav",
"store-integrations-nav"));
services.AddSingleton<IUIExtension>(new UIExtension("LNURL/LightningAddressOption",
"store-integrations-list"));
services.AddSingleton<IHostedService, LightningListener>();

View file

@ -15,7 +15,9 @@ namespace BTCPayServer.Plugins.Shopify
public override void Execute(IServiceCollection applicationBuilder)
{
applicationBuilder.AddSingleton<IHostedService, ShopifyOrderMarkerHostedService>();
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Shopify/StoreIntegrationShopifyOption",
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Shopify/StoreIntegrationsNav",
"store-integrations-nav"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Shopify/StoreIntegrationsList",
"store-integrations-list"));
base.Execute(applicationBuilder);
}

View file

@ -77,5 +77,7 @@ namespace BTCPayServer
return null;
}
internal bool ContainsFilter(string key) => Filters.ContainsKey(key);
}
}

View file

@ -1,8 +1,9 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.PaymentRequests;
@ -37,28 +38,32 @@ namespace BTCPayServer.Security
_invoiceRepository = invoiceRepository;
_paymentRequestRepository = paymentRequestRepository;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
if (context.User.Identity.AuthenticationType != AuthenticationSchemes.Cookie)
return;
var isAdmin = context.User.IsInRole(Roles.ServerAdmin);
switch (requirement.Policy)
{
case Policies.CanModifyServerSettings:
if (isAdmin)
context.Succeed(requirement);
return;
}
var userId = _userManager.GetUserId(context.User);
if (string.IsNullOrEmpty(userId))
return;
bool success = false;
var isAdmin = context.User.IsInRole(Roles.ServerAdmin);
AppData app = null;
StoreData store = null;
InvoiceEntity invoice = null;
PaymentRequestData paymentRequest = null;
string storeId = context.Resource is string s ? s : _httpContext.GetImplicitStoreId();
string storeId;
var explicitResource = false;
if (context.Resource is string s)
{
explicitResource = true;
storeId = s;
}
else
storeId = _httpContext.GetImplicitStoreId();
var routeData = _httpContext.GetRouteData();
if (routeData != null)
{
@ -81,28 +86,52 @@ namespace BTCPayServer.Security
{
string payReqId = vPayReqId as string;
paymentRequest = await _paymentRequestRepository.FindPaymentRequest(payReqId, userId);
storeId ??= paymentRequest?.StoreDataId;
if (storeId == null)
{
storeId = paymentRequest?.StoreDataId;
}
else if (paymentRequest?.StoreDataId != storeId)
{
paymentRequest = null;
}
}
// resolve from invoice
if (routeData.Values.TryGetValue("invoiceId", out var vInvoiceId))
{
string invoiceId = vInvoiceId as string;
invoice = await _invoiceRepository.GetInvoice(invoiceId);
storeId ??= invoice?.StoreId;
if (storeId == null)
{
storeId = invoice?.StoreId;
}
else if (invoice?.StoreId != storeId)
{
invoice = null;
}
}
}
// store could not be found
// Fall back to user prefs cookie
if (storeId == null)
{
return;
storeId = _httpContext.GetUserPrefsCookie()?.CurrentStoreId;
}
var store = await _storeRepository.FindStore(storeId, userId);
if (storeId != null)
{
store = await _storeRepository.FindStore(storeId, userId);
}
bool success = false;
switch (requirement.Policy)
{
case Policies.CanModifyServerSettings:
if (isAdmin)
success = true;
break;
case Policies.CanViewInvoices:
if (store == null || store.Role == StoreRoles.Owner || isAdmin)
success = true;
break;
case Policies.CanModifyStoreSettings:
if (store != null && (store.Role == StoreRoles.Owner || isAdmin))
success = true;
@ -115,17 +144,31 @@ namespace BTCPayServer.Security
if (store != null || isAdmin)
success = true;
break;
case Policies.CanViewProfile:
case Policies.CanViewNotificationsForUser:
case Policies.CanManageNotificationsForUser:
case Policies.CanModifyStoreSettingsUnscoped:
if (context.User != null)
success = true;
break;
}
if (success)
{
context.Succeed(requirement);
_httpContext.SetStoreData(store);
// cache associated entities if present
if (app != null) _httpContext.SetAppData(app);
if (invoice != null) _httpContext.SetInvoiceData(invoice);
if (paymentRequest != null) _httpContext.SetPaymentRequestData(paymentRequest);
if (!explicitResource)
{
if (store != null)
{
_httpContext.SetStoreData(store);
// cache associated entities if present
if (app != null) _httpContext.SetAppData(app);
if (invoice != null) _httpContext.SetInvoiceData(invoice);
if (paymentRequest != null) _httpContext.SetPaymentRequestData(paymentRequest);
}
}
}
}
}

View file

@ -12,6 +12,7 @@ namespace BTCPayServer.Security
{
return scopes.All(s => context.User.HasClaim(c => c.Type.Equals("scope", StringComparison.InvariantCultureIgnoreCase) && c.Value.Split(' ').Contains(s)));
}
public static string GetImplicitStoreId(this HttpContext httpContext)
{
// 1. Check in the routeData

View file

@ -2,6 +2,5 @@
@using BTCPayServer.Views
@using BTCPayServer.Views.Apps
@{
ViewBag.CategoryTitle = "Apps";
ViewData.SetActiveCategory(typeof(AppsNavPages));
}

View file

@ -1,4 +1,3 @@
@{
Layout = "_LayoutSimple";
ViewBag.TopSmallMargin = true;
}

View file

@ -2,9 +2,7 @@
@using BTCPayServer.Abstractions.Extensions
@model LNURLController.EditLightningAddressVM
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../Stores/_Nav";
ViewData.SetActivePage(StoreNavPages.Integrations, "Lightning Address Setup", Context.GetStoreData().StoreName);
ViewData.SetActivePage("LightningAddress", nameof(StoreNavPages), "Lightning Address", Context.GetStoreData().Id);
}
@section PageHeadContent {
@ -27,17 +25,19 @@
</script>
}
<partial name="_StatusMessage" />
@if (Context.Request.PathBase.ToString() != string.Empty)
{
<div class="alert alert-warning" role="alert">
Your BTCPay Server installation is using the root path <span class="fw-bold">@Context.Request.PathBase</span>.<br /><br />
This is incompatible with wallets attempting to resolve <span class="fw-bold">@Context.Request.GetAbsoluteUriNoPathBase(new Uri("/.well-known/lnurlp/{username}", UriKind.Relative))</span> rather than <span class="fw-bold">@Context.Request.GetAbsoluteUri("/.well-known/lnurlp/{username}")</span>.<br /><br />
If the LN Address doesn't work, ask your integrator to redirect queries from <span class="fw-bold">@Context.Request.GetAbsoluteUriNoPathBase(new Uri("/.well-known/lnurlp/{username}", UriKind.Relative))</span> to <span class="fw-bold">@Context.Request.GetAbsoluteUri("/.well-known/lnurlp/{username}")</span>.
</div>
<div class="alert alert-warning" role="alert">
Your BTCPay Server installation is using the root path <span class="fw-bold">@Context.Request.PathBase</span>.<br /><br />
This is incompatible with wallets attempting to resolve <span class="fw-bold">@Context.Request.GetAbsoluteUriNoPathBase(new Uri("/.well-known/lnurlp/{username}", UriKind.Relative))</span> rather than <span class="fw-bold">@Context.Request.GetAbsoluteUri("/.well-known/lnurlp/{username}")</span>.<br /><br />
If the LN Address doesn't work, ask your integrator to redirect queries from <span class="fw-bold">@Context.Request.GetAbsoluteUriNoPathBase(new Uri("/.well-known/lnurlp/{username}", UriKind.Relative))</span> to <span class="fw-bold">@Context.Request.GetAbsoluteUri("/.well-known/lnurlp/{username}")</span>.
</div>
}
<div class="d-sm-flex align-items-center justify-content-between mb-2">
<h2 class="mb-3 mb-sm-0">@ViewData["Title"]</h2>
<div class="d-flex align-items-center justify-content-between mb-2">
<h2 class="mb-0">@ViewData["Title"]</h2>
<a data-bs-toggle="collapse" data-bs-target="#AddAddress" class="btn btn-primary" role="button">
<span class="fa fa-plus"></span>
Add Address
@ -50,16 +50,16 @@
var showAdvancedOptions = !string.IsNullOrEmpty(Model.Add?.CurrencyCode) || Model.Add?.Min != null || Model.Add?.Max != null;
}
<div class="row collapse @(showAddForm ? "show": "")" id="AddAddress">
<div class="form-group pt-3">
<div class="collapse @(showAddForm ? "show": "")" id="AddAddress">
<div class="form-group pt-2">
<label asp-for="Add.Username" class="form-label"></label>
<div class="input-group">
<input asp-for="Add.Username" class="form-control"/>
<span class="input-group-text" >@@@Context.Request.Host.ToUriComponent()@Context.Request.PathBase</span>
<span class="input-group-text">@@@Context.Request.Host.ToUriComponent()@Context.Request.PathBase</span>
</div>
<span asp-validation-for="Add.Username" class="text-danger"></span>
</div>
<a class="mb-3" role="button" data-bs-toggle="collapse" data-bs-target="#AdvancedSettings">Advanced settings</a>
<a class="d-inline-block mb-3" role="button" data-bs-toggle="collapse" data-bs-target="#AdvancedSettings">Advanced settings</a>
<div id="AdvancedSettings" class="collapse @(showAdvancedOptions ? "show" : "")">
<div class="row">
<div class="col-12 col-sm-auto">
@ -93,54 +93,58 @@
@if (Model.Items.Any())
{
<table class="table table-hover">
<thead>
<tr>
<th>Address</th>
<th>Settings</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@for (var index = 0; index < Model.Items.Count; index++)
{
<input asp-for="Items[index].CurrencyCode" type="hidden"/>
<input asp-for="Items[index].Min" type="hidden"/>
<input asp-for="Items[index].Max" type="hidden"/>
<input asp-for="Items[index].Username" type="hidden"/>
var address = $"{Model.Items[index].Username}@{Context.Request.Host.ToUriComponent()}";
<tr>
<td>
<div class="input-group" data-clipboard="@address">
<input type="text" class="form-control copy-cursor lightning-address-value" readonly="readonly" value="@address"/>
<button type="button" class="btn btn-outline-secondary" data-clipboard-confirm>Copy</button>
</div>
<div class="row">
<div class="col">
<table class="table table-hover">
<thead>
<tr>
<th>Address</th>
<th>Settings</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
@for (var index = 0; index < Model.Items.Count; index++)
{
<input asp-for="Items[index].CurrencyCode" type="hidden"/>
<input asp-for="Items[index].Min" type="hidden"/>
<input asp-for="Items[index].Max" type="hidden"/>
<input asp-for="Items[index].Username" type="hidden"/>
var address = $"{Model.Items[index].Username}@{Context.Request.Host.ToUriComponent()}";
<tr>
<td>
<div class="input-group" data-clipboard="@address">
<input type="text" class="form-control copy-cursor lightning-address-value" readonly="readonly" value="@address"/>
<button type="button" class="btn btn-outline-secondary" data-clipboard-confirm>Copy</button>
</div>
</td>
<td class="settings-holder align-middle">
@if (Model.Items[index].Min.HasValue)
{
<span>@Safe.Raw($"{Model.Items[index].Min} min sats")</span>
}
@if (Model.Items[index].Max.HasValue)
{
<span> @Safe.Raw($"{Model.Items[index].Max} max sats")</span>
}
@if (!string.IsNullOrEmpty(Model.Items[index].CurrencyCode))
{
<span> @Safe.Raw($"tracked in {Model.Items[index].CurrencyCode}")</span>
}
</td>
<td class="text-end">
<button type="submit" title="Remove" name="command" value="@($"remove:{index}")"
class="btn btn-link px-0 remove" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The Lightning Address <strong>@address</strong> will be removed." data-confirm-input="REMOVE">
Remove
</button>
</td>
</tr>
}
</tbody>
</table>
</td>
<td class="settings-holder align-middle">
@if (Model.Items[index].Min.HasValue)
{
<span>@Safe.Raw($"{Model.Items[index].Min} min sats")</span>
}
@if (Model.Items[index].Max.HasValue)
{
<span> @Safe.Raw($"{Model.Items[index].Max} max sats")</span>
}
@if (!string.IsNullOrEmpty(Model.Items[index].CurrencyCode))
{
<span> @Safe.Raw($"tracked in {Model.Items[index].CurrencyCode}")</span>
}
</td>
<td class="text-end">
<button type="submit" title="Remove" name="command" value="@($"remove:{index}")"
class="btn btn-link px-0 remove" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The Lightning Address <strong>@address</strong> will be removed." data-confirm-input="REMOVE">
Remove
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{

View file

@ -16,13 +16,11 @@
<span class="text-capitalize badge bg-secondary">@item.Amount @cryptoCode</span>
</li>
<form method="post" class="list-group-item justify-content-center" id="pay-invoices-form">
<button type="submit" class="btn btn-primary xmx-2" style="min-width:25%;" id="Pay">Pay</button>
<button type="button" class="btn btn-secondary mx-2" onclick="history.back(); return false;" style="min-width:25%;">Go back</button>
</form>
}
</ul>
</div>
</div>
@ -30,12 +28,11 @@
@section PageFootContent {
<partial name="_ValidationScriptsPartial" />
<script>
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("DOMContentLoaded", function() {
$("#pay-invoices-form").on("submit", function() {
$(this).find("input[type='submit']").prop('disabled', true);
});
$("#pay-invoices-form input").on("input", function () {
$("#pay-invoices-form input").on("input", function() {
// Give it a timeout to make sure all form validation has completed by the time we run our callback
setTimeout(function() {
var validationErrors = $('.field-validation-error');
@ -47,4 +44,3 @@
});
</script>
}
</section>

View file

@ -3,6 +3,5 @@
@using BTCPayServer.Views.Manage
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewBag.CategoryTitle = "Account";
ViewData.SetActiveCategory(typeof(ManageNavPages));
}

View file

@ -5,7 +5,7 @@
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../Stores/_Nav";
ViewData.SetActivePage(StoreNavPages.ActivePage, $"{Model.CryptoCode} Settings");
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"{Model.CryptoCode} Settings");
}
<div class="row">

View file

@ -4,7 +4,7 @@
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.ActivePage, "Monero Settings");
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Monero Settings");
ViewData["NavPartialName"] = "../Stores/_Nav";
}

View file

@ -3,7 +3,7 @@
@model BTCPayServer.Models.PaymentRequestViewModels.UpdatePaymentRequestViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@{
ViewData.SetActivePage(PaymentRequestsNavPages.Create, (string.IsNullOrEmpty(Model.Id) ? "Create" : "Edit") + " Payment Request");
ViewData.SetActivePage(PaymentRequestsNavPages.Create, $"{(string.IsNullOrEmpty(Model.Id) ? "Create" : "Edit")} Payment Request", Model.Id);
}
@section PageHeadContent {

View file

@ -3,6 +3,7 @@
@model BTCPayServer.Controllers.ServerController.ListPluginsViewModel
@inject BTCPayServerOptions BTCPayServerOptions
@{
Layout = "_Layout";
ViewData.SetActivePage(ServerNavPages.Plugins);
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
var availableAndNotInstalled = Model.Available
@ -95,6 +96,9 @@
.version-switch .nav-link { display: inline; }
.version-switch .nav-link.active { display: none; }
</style>
<partial name="_StatusMessage" />
@if (Model.Disabled.Any())
{
<div class="alert alert-danger mb-5">

View file

@ -13,6 +13,6 @@
}
<a asp-controller="Server" id="SectionNav-@ServerNavPages.Logs" class="nav-link @ViewData.IsActivePage(ServerNavPages.Logs)" asp-action="LogsView">Logs</a>
<a asp-controller="Server" id="SectionNav-@ServerNavPages.Files" class="nav-link @ViewData.IsActivePage(ServerNavPages.Files)" asp-action="Files">Files</a>
<a asp-controller="Server" id="SectionNav-@ServerNavPages.Plugins" class="nav-link @ViewData.IsActivePage(ServerNavPages.Plugins)" asp-action="ListPlugins">Plugins (experimental)</a>
<vc:ui-extension-point location="server-nav" model="@Model"/>
</nav>

View file

@ -3,6 +3,5 @@
@using BTCPayServer.Views.Server
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewBag.CategoryTitle = "Server settings";
ViewData.SetActiveCategory(typeof(ServerNavPages));
}

View file

@ -2,21 +2,17 @@
@if (!string.IsNullOrEmpty(Model.ProvidedComment))
{
<tr>
<td colspan="100% bg-tile">
LNURL Comment: @Model.ProvidedComment
</td>
</tr>
<tr>
<td colspan="100% bg-tile">
LNURL Comment: @Model.ProvidedComment
</td>
</tr>
}
@if (!string.IsNullOrEmpty(Model.ConsumedLightningAddress))
{
<tr>
<td colspan="100% bg-tile">
Lightning address used: @Model.ConsumedLightningAddress
</td>
</tr>
<tr>
<td colspan="100% bg-tile">
Lightning address used: @Model.ConsumedLightningAddress
</td>
</tr>
}

View file

@ -0,0 +1,23 @@
@using BTCPayServer.Payments.Lightning
@using BTCPayServer.Views.Stores
@using BTCPayServer.Abstractions.Extensions
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@{
var store = Context.GetStoreData();
var isLightningEnabled = store.IsLightningEnabled(BTCPayNetworkProvider);
var isLNUrlEnabled = store.IsLNUrlEnabled(BTCPayNetworkProvider);
var possible =
isLightningEnabled &&
isLNUrlEnabled &&
store.GetSupportedPaymentMethods(BTCPayNetworkProvider).OfType<LNURLPaySupportedPaymentMethod>().Any(type => type.CryptoCode == "BTC");
}
@if (possible)
{
<li class="nav-item">
<a asp-area="" asp-controller="LNURL" asp-action="EditLightningAddress" asp-route-storeId="@store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage("LightningAddress", nameof(StoreNavPages))" id="StoreNav-LightningAddress">
<vc:icon symbol="wallet-lightning"/>
<span>Lightning Address</span>
</a>
</li>
}

View file

@ -0,0 +1,12 @@
@using BTCPayServer.Views.Stores
@using BTCPayServer.Abstractions.Extensions
@{
var store = Context.GetStoreData();
}
<li class="nav-item">
<a asp-area="" asp-controller="Shopify" asp-action="EditShopifyIntegration" asp-route-storeId="@store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage("shopify", nameof(StoreNavPages))" id="StoreNav-Shopify">
<vc:icon symbol="shopify"/>
<span>Shopify</span>
</a>
</li>

View file

@ -28,6 +28,7 @@
$text.removeAttribute('hidden')
$continue.setAttribute('disabled', 'disabled')
$inputText.textContent = confirmInput
$input.setAttribute("autocomplete", "off");
$input.addEventListener('input', event => {
event.target.value.trim() === confirmInput
? $continue.removeAttribute('disabled')

View file

@ -9,7 +9,7 @@
@await RenderSectionAsync("PageHeadContent", false)
</head>
<body>
<section class="content-wrapper @(ViewBag.TopSmallMargin != null ? "pt-4" : "")">
<section class="content-wrapper">
<!-- Dummy navbar-brand, hackish way to keep test AssertNoError passing -->
<div class="navbar-brand d-none"></div>
<div class="container">

View file

@ -2,23 +2,23 @@
@using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Plugins.Shopify.Models.ShopifySettings
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../Stores/_Nav";
ViewData.SetActivePage(StoreNavPages.Integrations, "Integrations");
ViewData.SetActivePage("shopify", nameof(StoreNavPages), "Shopify", Context.GetStoreData().Id);
var shopifyCredsSet = Model?.IntegratedAt.HasValue is true;
var shopifyUrl = Model?.ShopifyUrl;
}
<partial name="_StatusMessage"/>
<h2 class="mt-1 mb-4">
@ViewData["Title"]
<small>
<a href="https://docs.btcpayserver.org/Shopify" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</small>
</h2>
<form method="post" id="shopifyForm">
<h4 class="mb-3">
Shopify
<small>
<a href="https://docs.btcpayserver.org/Shopify" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a>
</small>
</h4>
@if (!shopifyCredsSet)
{
<p class="alert alert-info">Create a Shopify Private App with the permissions "Orders - Read and write"</p>
@ -39,7 +39,7 @@
{
<span class="input-group-text">https://</span>
}
<input asp-for="ShopName" class="form-control" readonly="@shopifyCredsSet" />
<input asp-for="ShopName" class="form-control" readonly="@shopifyCredsSet"/>
@if (!Model?.ShopName?.Contains(".") is true)
{
@ -51,13 +51,13 @@
<div class="form-group">
<label asp-for="ApiKey" class="form-label"></label>
<input asp-for="ApiKey" class="form-control" readonly="@shopifyCredsSet" />
<input asp-for="ApiKey" class="form-control" readonly="@shopifyCredsSet"/>
<span asp-validation-for="ApiKey" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="form-label"></label>
<input asp-for="Password" class="form-control" type="password" value="@Model?.Password" readonly="@shopifyCredsSet" />
<input asp-for="Password" class="form-control" type="password" value="@Model?.Password" readonly="@shopifyCredsSet"/>
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="alert alert-warning">
@ -65,7 +65,7 @@
In Shopify please paste following script at <a href="@shopifyUrl/admin/settings/checkout#PolarisTextField1" target="_blank" class="fw-bold" rel="noreferrer noopener"> Settings &gt; Checkout &gt; Order Processing &gt; Additional Scripts</a>
</p>
<kbd style="display: block; word-break: break-all;">
@($"<script src='{Url.Action("ShopifyJavascript", "Shopify", new {storeId = Context.GetRouteValue("storeId")}, Context.Request.Scheme)}'></script>")
@($"<script src='{Url.Action("ShopifyJavascript", "Shopify", new { storeId = Context.GetRouteValue("storeId") }, Context.Request.Scheme)}'></script>")
</kbd>
</div>
<p class="alert alert-warning">

View file

@ -2,14 +2,14 @@
@using BTCPayServer.Views.Stores
@model BTCPayServer.Models.WalletViewModels.NewPullPaymentModel
@{
ViewData.SetActivePage(StoreNavPages.PullPayments, "New pull payment", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.PullPayments, "New pull payment", Context.GetStoreData().Id);
}
<partial name="_StatusMessage" />
<div class="row">
<div class="col-md-6">
<h4 class="mb-3">@ViewData["Title"]</h4>
<h2 class="mb-3">@ViewData["Title"]</h2>
<form method="post"
asp-route-walletId="@Context.GetRouteValue("walletId")"

View file

@ -7,7 +7,7 @@
@inject IEnumerable<IPayoutHandler> PayoutHandlers;
@{
ViewData.SetActivePage(StoreNavPages.Payouts, $"Payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Payouts, $"Payouts{(string.IsNullOrEmpty(Model.PullPaymentName) ? string.Empty : " for pull payment " + Model.PullPaymentName)}", Context.GetStoreData().Id);
Model.PaginationQuery ??= new Dictionary<string, object>();
Model.PaginationQuery.Add("pullPaymentId", Model.PullPaymentId);
Model.PaginationQuery.Add("paymentMethodId", Model.PaymentMethodId);

View file

@ -3,7 +3,7 @@
@using BTCPayServer.Client
@model BTCPayServer.Models.WalletViewModels.PullPaymentsModel
@{
ViewData.SetActivePage(StoreNavPages.PullPayments, "Pull payments", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.PullPayments, "Pull payments", Context.GetStoreData().Id);
var nextStartDateSortOrder = (string)ViewData["NextStartSortOrder"];
string startDateSortOrder = null;
switch (nextStartDateSortOrder)

View file

@ -2,7 +2,7 @@
@model CheckoutAppearanceViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.CheckoutAppearance, "Checkout experience", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.CheckoutAppearance, "Checkout experience", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -1,7 +1,7 @@
@model CreateTokenViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Tokens, "Create New Token", Context.GetStoreData()?.StoreName);
ViewData.SetActivePage(StoreNavPages.Tokens, "Create New Token", Context.GetStoreData()?.Id);
ViewBag.HidePublicKey = ViewBag.HidePublicKey ?? false;
ViewBag.ShowStores = ViewBag.ShowStores ?? false;
}

View file

@ -1,7 +1,7 @@
@model BTCPayServer.Models.ServerViewModels.EmailsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.GeneralSettings, "Email Settings", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.GeneralSettings, "Email Settings", Context.GetStoreData().Id);
}
<partial name="EmailsBody" model="Model" />

View file

@ -1,7 +1,7 @@
@model GeneralSettingsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.GeneralSettings, "General Settings", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.GeneralSettings, "General Settings", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -4,7 +4,7 @@
var isHotWallet = Model.Method == WalletSetupMethod.HotWallet;
var type = isHotWallet ? "Hot" : "Watch-Only";
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, $"Create {Model.CryptoCode} {type} Wallet", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"Create {Model.CryptoCode} {type} Wallet", Context.GetStoreData().Id);
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
ViewData.Add(nameof(Model.SupportSegwit), Model.SupportSegwit);

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, $"Generate {Model.CryptoCode} Wallet", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"Generate {Model.CryptoCode} Wallet", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Confirm addresses", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Confirm addresses", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Import your wallet file", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Import your wallet file", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Connect your hardware wallet", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Connect your hardware wallet", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Scan QR code", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Scan QR code", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Enter the wallet seed", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Enter the wallet seed", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Enter your extended public key", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, "Enter your extended public key", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -3,7 +3,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, $"Import {Model.CryptoCode} Wallet", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"Import {Model.CryptoCode} Wallet", Context.GetStoreData().Id);
}
@section Navbar {

View file

@ -1,7 +1,7 @@
@model IntegrationsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Integrations, "Integrations", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Integrations, "Integrations", Context.GetStoreData().Id);
}
<div class="row">
@ -12,7 +12,7 @@
}
<ul class="list-group mb-3">
<vc:ui-extension-point location="store-integrations-list" model="@Model"></vc:ui-extension-point>
<vc:ui-extension-point location="store-integrations-list" model="@Model" />
</ul>
<h4 class="mt-5 mb-3">Other Integrations</h4>

View file

@ -2,7 +2,7 @@
@model LightningSettingsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, $"{Model.CryptoCode} Lightning Settings", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.LightningSettings, $"{Model.CryptoCode} Lightning Settings", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -1,7 +1,7 @@
@model TokensViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Tokens, "Access Tokens", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Tokens, "Access Tokens", Context.GetStoreData().Id);
}
@if (Model.StoreNotConfigured)

View file

@ -2,7 +2,7 @@
@using BTCPayServer.Client.Models;
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Webhooks, "Webhook Settings", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Webhooks, "Webhook Settings", Context.GetStoreData().Id);
}
@section PageHeadContent {

View file

@ -1,8 +1,7 @@
@inject BTCPayServer.Security.ContentSecurityPolicies csp
@model PayButtonViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
csp.AllowUnsafeHashes("onBTCPayFormSubmit(event);return false");
}
@ -69,10 +68,13 @@
</script>
}
<partial name="_StatusMessage" />
<h2 class="mt-1 mb-4">@ViewData["Title"]</h2>
<div id="payButtonCtrl">
<div class="row">
<div class="col-lg-7">
<h3 class="mb-3">@ViewData["Title"]</h3>
<div class="row">
<p>Configure your Pay Button, and the generated code will be displayed at the bottom of the page to copy into your project.</p>
<h4 class="mt-3 mb-3">General Settings</h4>

View file

@ -1,11 +1,11 @@
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
}
<h2 class="mt-1 mb-4">@ViewData["Title"]</h2>
<div class="row">
<div class="col-md-10">
<h4 class="mb-3">@ViewData["Title"]</h4>
<p>
To start using Pay Button, you need to enable this feature explicitly.
Once you do so, anyone could create an invoice on your store (via API, for example).

View file

@ -1,10 +1,9 @@
@using System.Text.RegularExpressions
@using BTCPayServer.Lightning
@using BTCPayServer.Services
@model PaymentMethodsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Wallets", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Wallets", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -1,7 +1,7 @@
@model BTCPayServer.Models.StoreViewModels.RatesViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Rates, "Rates", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Rates, "Rates", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -1,7 +1,7 @@
@model PairingModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Tokens, "Pairing Permission", Context.GetStoreData()?.StoreName);
ViewData.SetActivePage(StoreNavPages.Tokens, "Pairing Permission", Context.GetStoreData()?.Id);
}
<h3 class="mb-0">@ViewData["Title"]</h3>

View file

@ -1,7 +1,7 @@
@model LightningNodeViewModel
@{
Layout = "_LayoutWalletSetup.cshtml";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Connect to a Lightning node", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.LightningSettings, "Connect to a Lightning node", Context.GetStoreData().Id);
}
<header class="text-center">

View file

@ -1,7 +1,7 @@
@model WalletSetupViewModel
@{
Layout = "_LayoutWalletSetup";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, $"Setup {Model.CryptoCode} Wallet", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"Setup {Model.CryptoCode} Wallet", Context.GetStoreData().Id);
}
<h1 class="text-center">Let's get started</h1>

View file

@ -1,7 +1,7 @@
@model BTCPayServer.Security.Bitpay.BitTokenEntity
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Tokens, "Access Tokens", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Tokens, "Access Tokens", Context.GetStoreData().Id);
}
<h3 class="mb-4">@ViewData["Title"]</h3>

View file

@ -2,6 +2,6 @@ namespace BTCPayServer.Views.Stores
{
public enum StoreNavPages
{
Index, Create, Rates, PaymentMethods, CheckoutAppearance, GeneralSettings, Tokens, Users, PayButton, Integrations, Webhooks, ActivePage, PullPayments, Payouts
Index, Create, Rates, PaymentMethods, OnchainSettings, LightningSettings, CheckoutAppearance, GeneralSettings, Tokens, Users, PayButton, Integrations, Webhooks, PullPayments, Payouts
}
}

View file

@ -1,7 +1,7 @@
@model StoreUsersViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Users, "Store Users", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Users, "Store Users", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -2,7 +2,7 @@
@using BTCPayServer.Client.Models;
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Webhooks, "Send a test event to a webhook endpoint", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Webhooks, "Send a test event to a webhook endpoint", Context.GetStoreData().Id);
}
<div class="row">

View file

@ -4,7 +4,8 @@
@model WalletSettingsViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, $"{Model.CryptoCode} Wallet Settings", Context.GetStoreData().StoreName);
ViewData["NavPartialName"] = "../Wallets/_Nav";
ViewData.SetActivePage(StoreNavPages.OnchainSettings, $"{Model.CryptoCode} Wallet Settings", Context.GetStoreData().Id);
}
@section PageHeadContent {

View file

@ -1,7 +1,7 @@
@model WebhooksViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Webhooks, "Webhooks", Context.GetStoreData().StoreName);
ViewData.SetActivePage(StoreNavPages.Webhooks, "Webhooks", Context.GetStoreData().Id);
}
<div class="d-flex align-items-center justify-content-between mb-3">

View file

@ -6,7 +6,6 @@
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.GeneralSettings))" class="nav-link @ViewData.IsActivePage(StoreNavPages.GeneralSettings)" asp-controller="Stores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")">General Settings</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="Stores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@Context.GetRouteValue("storeId")">Users</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Context.GetRouteValue("storeId")">Pay Button</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@Context.GetRouteValue("storeId")">Integrations</a>
<a permission="@Policies.CanModifyStoreSettings" id="SectionNav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@Context.GetRouteValue("storeId")">Webhooks</a>
<vc:ui-extension-point location="store-nav" model="@Model" />

View file

@ -3,6 +3,5 @@
@using BTCPayServer.Views.Stores
@{
ViewBag.CategoryTitle = "Stores";
ViewData.SetActiveCategory(typeof(StoreNavPages));
}

View file

@ -69,13 +69,13 @@
}
</td>
<td style="text-align:right">
<a asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchTerm="storeid:@store.Id">Invoices</a><span> - </span>
<a asp-action="ListInvoices" asp-controller="Invoice" asp-route-storeId="@store.Id">Invoices</a><span> - </span>
<a asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@store.Id">Pull Payments</a>
@if (store.IsOwner)
{
<span> - </span>
<a asp-action="PaymentMethods" asp-controller="Stores" asp-route-storeId="@store.Id" id="update-store-@store.Id">Settings</a><span> - </span>
<a asp-action="DeleteStore" asp-controller="Stores" asp-route-storeId="@store.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@store.Name</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete</a><span> - </span>
<a asp-action="DeleteStore" asp-controller="Stores" asp-route-storeId="@store.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@store.Name</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete</a>
}
</td>
</tr>

View file

@ -1,14 +1,15 @@
@model SignWithSeedViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, "Sign PSBT", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.Send, "Sign PSBT", walletId);
}
@section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" id="GoBack">
<a asp-action="WalletPSBT" asp-route-walletId="@walletId" id="GoBack">
<vc:icon symbol="back" />
</a>
<a asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" class="cancel">
<a asp-action="WalletSend" asp-route-walletId="@walletId" class="cancel">
<vc:icon symbol="close" />
</a>
}
@ -39,7 +40,7 @@
<div asp-validation-summary="All" class="text-danger"></div>
<form method="post" asp-action="SignWithSeed" asp-route-walletId="@Context.GetRouteValue("walletId")">
<form method="post" asp-action="SignWithSeed" asp-route-walletId="@walletId">
<partial name="SigningContext" for="SigningContext"/>
<div class="form-group">
<label asp-for="SeedOrKey" class="form-label"></label>

View file

@ -1,8 +1,9 @@
@model WalletPSBTViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.PSBT, "Decode PSBT", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.PSBT, "Decode PSBT", walletId);
}
@section PageHeadContent {
@ -41,7 +42,7 @@
}
<p>You can decode a PSBT by either pasting its content, uploading the file or scanning the wallet QR code.</p>
<form class="form-group" method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" enctype="multipart/form-data">
<form class="form-group" method="post" asp-action="WalletPSBT" asp-route-walletId="@walletId" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="PSBT" class="form-label"></label>
<textarea class="form-control" rows="5" asp-for="PSBT"></textarea>

View file

@ -1,7 +1,8 @@
@model WalletPSBTCombineViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.PSBT, "Combine PSBT", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.PSBT, "Combine PSBT", walletId);
}
@section Navbar {

View file

@ -1,11 +1,12 @@
@model WalletPSBTViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@{
var walletId = Context.GetRouteValue("walletId").ToString();
var isReady = !Model.HasErrors;
var isSignable = !isReady && Model.NBXSeedAvailable;
var needsExport = !isSignable && !isReady;
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.PSBT, isReady ? "Confirm broadcasting this transaction" : "Transaction Details", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.PSBT, isReady ? "Confirm broadcasting this transaction" : "Transaction Details", walletId);
}
@section PageHeadContent {
@ -49,7 +50,7 @@
@if (isSignable)
{
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="my-5">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@walletId" class="my-5">
<input type="hidden" asp-for="CryptoCode"/>
<input type="hidden" asp-for="NBXSeedAvailable"/>
<input type="hidden" asp-for="PSBT"/>
@ -61,7 +62,7 @@
}
else if (isReady)
{
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@Context.GetRouteValue("walletId")" class="my-5">
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@walletId" class="my-5">
<input type="hidden" asp-for="SigningKey" />
<input type="hidden" asp-for="SigningKeyPath" />
<partial name="SigningContext" for="SigningContext" />
@ -97,7 +98,7 @@ else
</h2>
<div id="PSBTOptionsExportContent" class="accordion-collapse collapse @(needsExport ? "show" : "")" aria-labelledby="PSBTOptionsExportHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="mb-2">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@walletId" class="mb-2">
<input type="hidden" asp-for="CryptoCode"/>
<input type="hidden" asp-for="PSBT"/>
<div class="d-flex flex-column flex-sm-row flex-wrap align-items-sm-center">
@ -146,7 +147,7 @@ else
</h2>
<div id="PSBTOptionsImportContent" class="accordion-collapse collapse" aria-labelledby="PSBTOptionsImportHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" enctype="multipart/form-data" class="mb-2">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@walletId" enctype="multipart/form-data" class="mb-2">
<div class="form-group">
<label for="ImportedPSBT" class="form-label">PSBT content</label>
<textarea id="ImportedPSBT" name="PSBT" class="form-control" rows="5"></textarea>
@ -173,7 +174,7 @@ else
</h2>
<div id="PSBTOptionsAdvancedContent" class="accordion-collapse collapse" aria-labelledby="PSBTOptionsAdvancedHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="mb-2">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@walletId" class="mb-2">
<input type="hidden" asp-for="CryptoCode"/>
<input type="hidden" asp-for="NBXSeedAvailable"/>
<input type="hidden" asp-for="PSBT"/>

View file

@ -1,8 +1,9 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@model BTCPayServer.Controllers.WalletReceiveViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.Receive, $"Receive {Model.CryptoCode}", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.Receive, $"Receive {Model.CryptoCode}", walletId);
}
@section PageHeadContent

View file

@ -1,7 +1,8 @@
@model RescanWalletModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.Rescan, "Rescan wallet", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.Rescan, "Rescan wallet", walletId);
}
<h4 class="mb-3">@ViewData["Title"]</h4>

View file

@ -3,8 +3,9 @@
@using Microsoft.AspNetCore.Mvc.ModelBinding
@model WalletSendModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.Send, $"Send {Model.CryptoCode}", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.Send, $"Send {Model.CryptoCode}", walletId);
csp.Add("worker-src", "blob:");
}
@ -30,7 +31,7 @@
<div class="row">
<div class="col-lg-8 col-xl-7 @(!Model.InputSelection && Model.Outputs.Count == 1 ? "transaction-output-form" : "")">
<h4 class="mb-3">@ViewData["Title"]</h4>
<form method="post" asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")">
<form method="post" asp-action="WalletSend" asp-route-walletId="@walletId">
<input type="hidden" asp-for="InputSelection" />
<input type="hidden" asp-for="FiatDivisibility" />
<input type="hidden" asp-for="CryptoDivisibility" />

View file

@ -1,14 +1,15 @@
@model WalletSendVaultModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, "Sign the transaction", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.Send, "Sign the transaction", walletId);
}
@section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" id="GoBack">
<a asp-action="WalletPSBT" asp-route-walletId="@walletId" id="GoBack">
<vc:icon symbol="back" />
</a>
<a asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" class="cancel">
<a asp-action="WalletSend" asp-route-walletId="@walletId" class="cancel">
<vc:icon symbol="close" />
</a>
}
@ -26,7 +27,7 @@
</div>
<div id="body" class="my-4">
<form id="broadcastForm" asp-action="WalletSendVault" asp-route-walletId="@Context.GetRouteValue("walletId")" method="post" style="display:none;">
<form id="broadcastForm" asp-action="WalletSendVault" asp-route-walletId="@walletId" method="post" style="display:none;">
<input type="hidden" id="WalletId" asp-for="WalletId" />
<input type="hidden" asp-for="WebsocketPath" />
<partial name="SigningContext" for="SigningContext" />

View file

@ -2,9 +2,9 @@
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, "Sign the transaction", Context.GetStoreData().StoreName);
var walletId = WalletId.Parse(Context.GetRouteValue("walletId").ToString());
Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, "Sign the transaction", walletId.ToString());
}
@section Navbar {

View file

@ -1,7 +1,8 @@
@model ListTransactionsViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", Context.GetStoreData().StoreName);
ViewData.SetActivePage(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", walletId);
}
@section PageHeadContent {

View file

@ -7,6 +7,7 @@ namespace BTCPayServer.Views.Wallets
Transactions,
Rescan,
PSBT,
Receive
Receive,
Settings
}
}

View file

@ -1,20 +1,25 @@
@inject SignInManager<ApplicationUser> SignInManager
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@using BTCPayServer.Views.Stores
@using BTCPayServer.Client
@inject BTCPayNetworkProvider _btcPayNetworkProvider
@{
var wallet = WalletId.Parse(Context.GetRouteValue("walletId").ToString());
var network = BtcPayNetworkProvider.GetNetwork<BTCPayNetwork>(wallet.CryptoCode);
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" class="nav">
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Transactions)" asp-action="WalletTransactions" asp-route-walletId="@Context.GetRouteValue("walletId")" id="SectionNav-Transactions">Transactions</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Transactions)" asp-controller="Wallets" 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-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" id="SectionNav-Send">Send</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Send)" asp-controller="Wallets" asp-action="WalletSend" asp-route-walletId="@wallet" id="SectionNav-Send" permission="@Policies.CanModifyStoreSettings">Send</a>
}
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Receive)" asp-action="WalletReceive" asp-route-walletId="@Context.GetRouteValue("walletId")" id="SectionNav-Receive">Receive</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")" id="SectionNav-Rescan">Rescan</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Receive)" asp-controller="Wallets" 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="Wallets" 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-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" id="SectionNav-PSBT">PSBT</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-controller="Wallets" asp-action="WalletPSBT" asp-route-walletId="@wallet" id="SectionNav-PSBT" permission="@Policies.CanModifyStoreSettings">PSBT</a>
}
<vc:ui-extension-point location="wallet-nav" model="@Model" />
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Settings) @ViewData.IsActivePage(StoreNavPages.OnchainSettings)" asp-controller="Stores" 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"/>
</nav>

View file

@ -2,6 +2,5 @@
@using BTCPayServer.Views
@using BTCPayServer.Views.Wallets
@{
ViewBag.CategoryTitle = "Wallets";
ViewData.SetActiveCategory(typeof(WalletsNavPages));
}

View file

@ -29,4 +29,5 @@
<symbol id="payment-1" viewBox="0 0 24 24"><path d="M7.2 11.2h9.6v1.6H7.2v-1.6Zm0 4.8h5.6v-1.6H7.2V16ZM20 7.02v9.96c0 1.23-1.07 2.22-2.4 2.22H6.4c-1.31 0-2.4-1-2.4-2.22V7.02C4 5.81 5.09 4.8 6.4 4.8h11.2c1.31 0 2.4 1 2.4 2.22ZM18.4 12V7.02c0-.33-.38-.62-.8-.62H6.4c-.43 0-.8.29-.8.62v9.96c0 .33.37.62.8.62h11.2c.43 0 .8-.29.8-.62V12ZM7.2 9.6h9.6V8H7.2v1.6Z" fill="currentColor"/></symbol>
<symbol id="payment-2" viewBox="0 0 24 24"><path d="M12 20a8 8 0 1 1 0-16 8 8 0 0 1 0 16Zm0-15.19a7.2 7.2 0 0 0 0 14.38A7.2 7.2 0 0 0 12 4.8Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M9.48 14.85a.44.44 0 0 1-.3-.14c-.14-.16-.14-.43.05-.57l5.02-4.31c.16-.14.43-.14.57.05.14.17.14.44-.05.57l-5.05 4.29c-.05.08-.16.1-.24.1Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M14.39 14.28a.4.4 0 0 1-.41-.4l.1-3.42-3.08-.17a.4.4 0 0 1-.38-.43c0-.22.19-.4.43-.38l3.47.19c.22 0 .38.19.38.4l-.13 3.83c.02.19-.17.38-.38.38Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/></symbol>
<symbol id="invoice" viewBox="0 0 24 24"><path d="M17.1 20H6.9c-.83 0-1.53-.7-1.53-1.52V5.52c0-.82.7-1.52 1.52-1.52h10.22c.83 0 1.52.7 1.52 1.52v12.96c0 .82-.7 1.52-1.52 1.52ZM6.9 5.3c-.14 0-.23.1-.23.22v12.96c0 .13.1.22.22.22h10.22c.13 0 .22-.1.22-.22V5.52c0-.13-.09-.22-.22-.22H6.89Z" fill="currentColor"/><path d="M12.24 7.95H8.11c-.09 0-.13-.05-.13-.15v-1c0-.05.04-.1.09-.1h4.13c.04 0 .08.05.08.1v1c.05.1 0 .15-.04.15ZM16.2 17.6H8.1c-.08 0-.12-.08-.12-.12V9.87a.1.1 0 0 1 .09-.09h8.08a.1.1 0 0 1 .09.09v7.44c0 .11.06.3-.04.3Z" fill="currentColor"/></symbol>
<symbol id="shopify" viewBox="0 0 32 32"><path transform="scale(.7) translate(5, 5)" d="m20.45 31.97 9.62-2.08-3.5-23.64c-.03-.16-.15-.26-.28-.26l-2.57-.18s-1.7-1.7-1.92-1.88a.41.41 0 0 0-.16-.1l-1.22 28.14zm-4.83-16.9s-1.09-.56-2.37-.56c-1.93 0-2 1.2-2 1.52 0 1.64 4.31 2.29 4.31 6.17 0 3.06-1.92 5.01-4.54 5.01-3.14 0-4.72-1.95-4.72-1.95l.86-2.78s1.66 1.42 3.04 1.42c.9 0 1.3-.72 1.3-1.24 0-2.16-3.54-2.26-3.54-5.81-.04-2.98 2.1-5.9 6.44-5.9 1.68 0 2.5.49 2.5.49l-1.26 3.62zM14.9 1.1c.17 0 .36.06.53.19-1.31.62-2.75 2.18-3.34 5.32-.88.28-1.73.54-2.52.77.69-2.38 2.36-6.26 5.33-6.26zm1.64 3.94v.18l-3.2.98c.63-2.37 1.79-3.53 2.79-3.96.26.67.41 1.57.41 2.8zm.72-2.98c.92.1 1.52 1.15 1.9 2.34-.46.15-.98.3-1.54.49v-.34c0-1-.13-1.82-.36-2.5zm3.99 1.72-.1.03c-.03 0-.39.1-.96.28-.56-1.65-1.56-3.16-3.34-3.16h-.16C16.2.28 15.56 0 15.02 0 10.88 0 8.9 5.17 8.28 7.8c-1.6.48-2.75.84-2.88.9-.9.28-.93.3-1.03 1.15-.1.62-2.44 18.75-2.44 18.75L20.01 32z" fill="currentColor"/></symbol>
</svg>

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -8,9 +8,7 @@
:root {
--mobile-header-height: 4rem;
--desktop-header-height: 8rem;
--sidebar-width: 15%;
--sidebar-min-width: 250px;
--sidebar-max-width: 350px;
--sidebar-width: 280px;
}
/* Main Menu */
@ -278,8 +276,6 @@
bottom: 0;
left: 0;
width: var(--sidebar-width);
min-width: var(--sidebar-min-width);
max-width: var(--sidebar-max-width);
z-index: 1045;
color: var(--btcpay-body-text);
visibility: hidden;
@ -381,8 +377,6 @@
bottom: 0;
left: 0;
width: var(--sidebar-width);
min-width: var(--sidebar-min-width);
max-width: var(--sidebar-max-width);
height: 100vh;
}
@ -412,7 +406,7 @@
}
#mainContent {
margin-left: clamp(var(--sidebar-min-width), var(--sidebar-width), var(--sidebar-max-width));
margin-left: var(--sidebar-width);
}
#mainContent > section {