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 97e8b8379c2f2f373bac15a96632d2c8913ef4bd.

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

View File

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

View File

@@ -28,17 +28,17 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(); s.RegisterNewUser();
var store = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC"); 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.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).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.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput")); s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome(); s.GoToHome();
var invoiceId = s.CreateInvoice(store.storeName); s.CreateInvoice(storeId);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click(); s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError(); s.Driver.AssertNoError();
s.Driver.Navigate().Back(); s.Driver.Navigate().Back();
@@ -79,11 +79,11 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(); s.RegisterNewUser();
var store = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC"); s.AddDerivationScheme("BTC");
// Now create an invoice that requires a refund email // 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); s.GoToInvoiceCheckout(invoice);
var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput")); var emailInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
@@ -106,7 +106,7 @@ namespace BTCPayServer.Tests
s.GoToHome(); s.GoToHome();
// Now create an invoice that doesn't require a refund email // 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.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError(); s.Driver.AssertNoError();
s.Driver.Navigate().Back(); s.Driver.Navigate().Back();
@@ -119,7 +119,7 @@ namespace BTCPayServer.Tests
s.GoToHome(); s.GoToHome();
// Now create an invoice that requires refund email but already has one set, email input shouldn't show up // 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.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError(); s.Driver.AssertNoError();
s.Driver.Navigate().Back(); s.Driver.Navigate().Back();
@@ -139,10 +139,10 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(); s.RegisterNewUser();
var store = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.AddDerivationScheme("BTC"); s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(store.storeName); var invoiceId = s.CreateInvoice(storeId);
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
Assert.True(s.Driver.FindElement(By.Id("DefaultLang")).FindElements(By.TagName("option")).Count > 1); 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; var payWithTextEnglish = s.Driver.FindElement(By.Id("pay-with-text")).Text;
@@ -171,11 +171,11 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(true); s.RegisterNewUser(true);
var store = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.AddLightningNode(); s.AddLightningNode();
s.AddDerivationScheme("BTC"); s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(store.storeName, defaultPaymentMethod: "BTC_LightningLike"); var invoiceId = s.CreateInvoice(storeId, defaultPaymentMethod: "BTC_LightningLike");
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text); Assert.Equal("Bitcoin (Lightning) (BTC)", s.Driver.FindElement(By.ClassName("payment__currencies")).Text);
@@ -193,7 +193,7 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(true); s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.AddLightningNode(); s.AddLightningNode();
s.GoToStore(storeId); s.GoToStore(storeId);
s.Driver.FindElement(By.Id("Modify-LightningBTC")).Click(); s.Driver.FindElement(By.Id("Modify-LightningBTC")).Click();
@@ -201,7 +201,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("save")).Click(); s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains("BTC Lightning settings successfully updated", s.FindAlertMessage().Text); 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); s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text); Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
} }
@@ -215,10 +215,10 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
s.GoToRegister(); s.GoToRegister();
s.RegisterNewUser(); s.RegisterNewUser();
var store = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.GoToStore(store.storeId); s.GoToStore(storeId);
s.AddDerivationScheme(); 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); var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
s.Driver.Navigate() s.Driver.Navigate()
.GoToUrl(new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}")); .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(senderWalletId);
await s.FundStoreWallet(receiverWalletId); await s.FundStoreWallet(receiverWalletId);
var invoiceId = s.CreateInvoice(receiver.storeName, null, "BTC"); var invoiceId = s.CreateInvoice(receiver.storeId, null, "BTC");
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href"); .GetAttribute("href");
@@ -295,7 +295,7 @@ namespace BTCPayServer.Tests
var receiverWalletId = new WalletId(receiver.storeId, cryptoCode); var receiverWalletId = new WalletId(receiver.storeId, cryptoCode);
//payjoin is enabled by default. //payjoin is enabled by default.
var invoiceId = s.CreateInvoice(receiver.storeName); var invoiceId = s.CreateInvoice(receiver.storeId);
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) var bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href"); .GetAttribute("href");
@@ -310,7 +310,7 @@ namespace BTCPayServer.Tests
await s.Server.ExplorerNode.GenerateAsync(1); await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(senderWalletId); await s.FundStoreWallet(senderWalletId);
invoiceId = s.CreateInvoice(receiver.storeName); invoiceId = s.CreateInvoice(receiver.storeId);
s.GoToInvoiceCheckout(invoiceId); s.GoToInvoiceCheckout(invoiceId);
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href"); .GetAttribute("href");
@@ -343,7 +343,7 @@ namespace BTCPayServer.Tests
StringComparison.InvariantCultureIgnoreCase)); StringComparison.InvariantCultureIgnoreCase));
//let's do it all again, except now the receiver has funds and is able to payjoin //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); s.GoToInvoiceCheckout(invoiceId);
bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn")) bip21 = s.Driver.FindElement(By.ClassName("payment__details__instruction__open-wallet__btn"))
.GetAttribute("href"); .GetAttribute("href");
@@ -393,7 +393,7 @@ namespace BTCPayServer.Tests
var dto = invoice.EntityToDTO(); var dto = invoice.EntityToDTO();
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status); Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
}); });
s.GoToInvoices(); s.GoToInvoices(receiver.storeId);
paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_details_{invoiceId}")) paymentValueRowColumn = s.Driver.FindElement(By.Id($"invoice_details_{invoiceId}"))
.FindElement(By.ClassName("payment-value")); .FindElement(By.ClassName("payment-value"));
Assert.False(paymentValueRowColumn.Text.Contains("payjoin", Assert.False(paymentValueRowColumn.Text.Contains("payjoin",

View File

@@ -333,16 +333,9 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("LoginButton")).Click(); 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) public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.PaymentMethods)
{ {
GoToHome(); GoToUrl($"/stores/{storeId}/");
Driver.WaitForAndClick(By.Id("StoreSelectorToggle"));
Driver.WaitForAndClick(By.Id($"StoreSelectorMenuItem-{storeId}"));
if (storeNavPage != StoreNavPages.PaymentMethods) if (storeNavPage != StoreNavPages.PaymentMethods)
{ {
@@ -359,10 +352,17 @@ namespace BTCPayServer.Tests
} }
public void GoToWalletSettings(string storeId, string cryptoCode = "BTC") public void GoToWalletSettings(string storeId, string cryptoCode = "BTC")
{
try
{ {
GoToStore(storeId); GoToStore(storeId);
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
} }
catch (NoSuchElementException)
{
GoToWallet(new WalletId(storeId, cryptoCode), WalletsNavPages.Settings);
}
}
public void GoToLightningSettings(string storeId, string cryptoCode = "BTC") public void GoToLightningSettings(string storeId, string cryptoCode = "BTC")
{ {
@@ -377,10 +377,14 @@ namespace BTCPayServer.Tests
CheckForJSErrors(); CheckForJSErrors();
} }
public void GoToInvoices() public void GoToInvoice(string id)
{ {
GoToHome(); GoToUrl($"/invoices/{id}/");
Driver.FindElement(By.Id("Nav-Invoices")).Click(); }
public void GoToInvoices(string storeId = null)
{
GoToUrl(storeId == null ? "/invoices/" : $"/stores/{storeId}/invoices/");
} }
public void GoToProfile(ManageNavPages navPages = ManageNavPages.Index) public void GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
@@ -394,11 +398,11 @@ namespace BTCPayServer.Tests
public void GoToLogin() public void GoToLogin()
{ {
Driver.Navigate().GoToUrl(new Uri(ServerUri, "/login")); GoToUrl("/login");
} }
public string CreateInvoice( public string CreateInvoice(
string storeName, string storeId,
decimal? amount = 100, decimal? amount = 100,
string currency = "USD", string currency = "USD",
string refundEmail = "", string refundEmail = "",
@@ -407,7 +411,7 @@ namespace BTCPayServer.Tests
StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success StatusMessageModel.StatusSeverity expectedSeverity = StatusMessageModel.StatusSeverity.Success
) )
{ {
GoToInvoices(); GoToInvoices(storeId);
Driver.FindElement(By.Id("CreateNewInvoice")).Click(); Driver.FindElement(By.Id("CreateNewInvoice")).Click();
if (amount is decimal v) if (amount is decimal v)
Driver.FindElement(By.Id("Amount")).SendKeys(v.ToString(CultureInfo.InvariantCulture)); Driver.FindElement(By.Id("Amount")).SendKeys(v.ToString(CultureInfo.InvariantCulture));
@@ -415,7 +419,6 @@ namespace BTCPayServer.Tests
currencyEl.Clear(); currencyEl.Clear();
currencyEl.SendKeys(currency); currencyEl.SendKeys(currency);
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail); Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
if (defaultPaymentMethod is string) if (defaultPaymentMethod is string)
new SelectElement(Driver.FindElement(By.Name("DefaultPaymentMethod"))).SelectByValue(defaultPaymentMethod); new SelectElement(Driver.FindElement(By.Name("DefaultPaymentMethod"))).SelectByValue(defaultPaymentMethod);
if (requiresRefundEmail is bool) if (requiresRefundEmail is bool)
@@ -501,18 +504,5 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id($"SectionNav-{navPages}")).Click(); 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(); s.Server.ActivateLightning();
await s.StartAsync(); await s.StartAsync();
var alice = s.RegisterNewUser(true); 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 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."; 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; var storeUrl = s.Driver.Url;
s.ClickOnAllSectionLinks(); s.ClickOnAllSectionLinks();
s.GoToInvoices(); s.GoToInvoices(storeId);
var invoiceId = s.CreateInvoice(storeName); var invoiceId = s.CreateInvoice(storeId);
s.FindAlertMessage(); s.FindAlertMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click(); s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url; var invoiceUrl = s.Driver.Url;
@@ -402,16 +402,17 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text); Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click(); s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
Assert.Contains("Unarchive", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text); Assert.Contains("Unarchive", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
//check that it no longer appears in list
s.GoToInvoices();
//check that it no longer appears in list
s.GoToInvoices(storeId);
Assert.DoesNotContain(invoiceId, s.Driver.PageSource); Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
//ok, let's unarchive and see that it shows again //ok, let's unarchive and see that it shows again
s.Driver.Navigate().GoToUrl(invoiceUrl); s.Driver.Navigate().GoToUrl(invoiceUrl);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click(); s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
s.FindAlertMessage(); s.FindAlertMessage();
Assert.DoesNotContain("Unarchive", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text); Assert.DoesNotContain("Unarchive", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.GoToInvoices(); s.GoToInvoices(storeId);
Assert.Contains(invoiceId, s.Driver.PageSource); Assert.Contains(invoiceId, s.Driver.PageSource);
// When logout out we should not be able to access store and invoice details // When logout out we should not be able to access store and invoice details
@@ -614,7 +615,7 @@ namespace BTCPayServer.Tests
{ {
await s.StartAsync(); await s.StartAsync();
s.RegisterNewUser(true); s.RegisterNewUser(true);
var (_, storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.GenerateWallet("BTC", "", false, true); s.GenerateWallet("BTC", "", false, true);
var walletId = new WalletId(storeId, "BTC"); var walletId = new WalletId(storeId, "BTC");
s.GoToWallet(walletId, WalletsNavPages.Receive); s.GoToWallet(walletId, WalletsNavPages.Receive);
@@ -676,7 +677,7 @@ namespace BTCPayServer.Tests
{ {
await s.StartAsync(); await s.StartAsync();
s.RegisterNewUser(true); s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
s.GoToStore(storeId, StoreNavPages.Webhooks); s.GoToStore(storeId, StoreNavPages.Webhooks);
TestLogs.LogInformation("Let's create two 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"); TestLogs.LogInformation("Let's see if we can generate an event");
s.GoToStore(storeId); s.GoToStore(storeId);
s.AddDerivationScheme(); s.AddDerivationScheme();
s.CreateInvoice(storeName); s.CreateInvoice(storeId);
var request = await server.GetNextRequest(); var request = await server.GetNextRequest();
var headers = request.Request.Headers; var headers = request.Request.Headers;
var actualSig = headers["BTCPay-Sig"].First(); var actualSig = headers["BTCPay-Sig"].First();
@@ -745,14 +746,14 @@ namespace BTCPayServer.Tests
server.Done(); server.Done();
TestLogs.LogInformation("Let's make a failed event"); TestLogs.LogInformation("Let's make a failed event");
s.CreateInvoice(storeName); s.CreateInvoice(storeId);
request = await server.GetNextRequest(); request = await server.GetNextRequest();
request.Response.StatusCode = 404; request.Response.StatusCode = 404;
server.Done(); server.Done();
// The delivery is done asynchronously, so small wait here // The delivery is done asynchronously, so small wait here
await Task.Delay(500); await Task.Delay(500);
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks); s.GoToStore(storeId, StoreNavPages.Webhooks);
s.Driver.FindElement(By.LinkText("Modify")).Click(); s.Driver.FindElement(By.LinkText("Modify")).Click();
var elements = s.Driver.FindElements(By.ClassName("redeliver")); var elements = s.Driver.FindElements(By.ClassName("redeliver"));
// One worked, one failed // One worked, one failed
@@ -768,7 +769,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("Can we browse the json content?"); TestLogs.LogInformation("Can we browse the json content?");
CanBrowseContent(s); CanBrowseContent(s);
s.GoToInvoices(); s.GoToInvoices(storeId);
s.Driver.FindElement(By.LinkText("Details")).Click(); s.Driver.FindElement(By.LinkText("Details")).Click();
CanBrowseContent(s); CanBrowseContent(s);
var element = s.Driver.FindElement(By.ClassName("redeliver")); var element = s.Driver.FindElement(By.ClassName("redeliver"));
@@ -798,7 +799,7 @@ namespace BTCPayServer.Tests
foreach (var isHotwallet in new[] { false, true }) foreach (var isHotwallet in new[] { false, true })
{ {
var cryptoCode = "BTC"; 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.GenerateWallet(cryptoCode, "melody lizard phrase voice unique car opinion merge degree evil swift cargo", privkeys: isHotwallet);
s.GoToWalletSettings(storeId, cryptoCode); s.GoToWalletSettings(storeId, cryptoCode);
if (isHotwallet) if (isHotwallet)
@@ -816,7 +817,7 @@ namespace BTCPayServer.Tests
{ {
await s.StartAsync(); await s.StartAsync();
s.RegisterNewUser(true); s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
const string cryptoCode = "BTC"; const string cryptoCode = "BTC";
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', // 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")); 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 invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
var address = invoice.EntityToDTO().Addresses["BTC"]; var address = invoice.EntityToDTO().Addresses["BTC"];
@@ -878,7 +879,7 @@ namespace BTCPayServer.Tests
//lets import and save private keys //lets import and save private keys
var root = mnemonic.DeriveExtKey(); var root = mnemonic.DeriveExtKey();
invoiceId = s.CreateInvoice(storeName); invoiceId = s.CreateInvoice(storeId);
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId); invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
address = invoice.EntityToDTO().Addresses["BTC"]; address = invoice.EntityToDTO().Addresses["BTC"];
result = await s.Server.ExplorerNode.GetAddressInfoAsync( result = await s.Server.ExplorerNode.GetAddressInfoAsync(
@@ -978,8 +979,8 @@ namespace BTCPayServer.Tests
{ {
await s.StartAsync(); await s.StartAsync();
s.RegisterNewUser(true); s.RegisterNewUser(true);
(string _, string storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
var cryptoCode = "BTC"; const string cryptoCode = "BTC";
var mnemonic = s.GenerateWallet(cryptoCode, "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel"); var mnemonic = s.GenerateWallet(cryptoCode, "click chunk owner kingdom faint steak safe evidence bicycle repeat bulb wheel");
// Make sure wallet info is correct // Make sure wallet info is correct
@@ -1294,7 +1295,7 @@ namespace BTCPayServer.Tests
new[] { s.Server.MerchantLightningD }, new[] { s.Server.MerchantLightningD },
new[] { s.Server.MerchantLnd.Client }); new[] { s.Server.MerchantLnd.Client });
s.RegisterNewUser(true); s.RegisterNewUser(true);
(string storeName, string storeId) = s.CreateNewStore(); (_, string storeId) = s.CreateNewStore();
var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork; var network = s.Server.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode).NBitcoinNetwork;
s.GoToStore(storeId); s.GoToStore(storeId);
s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning, false); s.AddLightningNode(cryptoCode, LightningConnectionType.CLightning, false);
@@ -1307,7 +1308,7 @@ namespace BTCPayServer.Tests
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode); SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
// Topup Invoice test // Topup Invoice test
var i = s.CreateInvoice(storeName, null, cryptoCode); var i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i); s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.Id("copy-tab")).Click(); s.Driver.FindElement(By.Id("copy-tab")).Click();
var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value"); var lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
@@ -1339,8 +1340,8 @@ namespace BTCPayServer.Tests
}); });
// Standard invoice test // Standard invoice test
s.GoToHome(); s.GoToStore(storeId);
i = s.CreateInvoice(storeName, 0.0000001m, cryptoCode); i = s.CreateInvoice(storeId, 0.0000001m, cryptoCode);
s.GoToInvoiceCheckout(i); s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies")).Click(); s.Driver.FindElement(By.ClassName("payment__currencies")).Click();
// BOLT11 is also available for standard invoices // BOLT11 is also available for standard invoices
@@ -1382,12 +1383,12 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("save")).Click(); s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); 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.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.GoToHome(); s.GoToStore(storeId);
i = s.CreateInvoice(storeName, null, cryptoCode); i = s.CreateInvoice(storeId, null, cryptoCode);
s.GoToInvoiceCheckout(i); s.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); 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! //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); 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 // 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.GoToInvoiceCheckout(i);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.Driver.FindElement(By.Id("copy-tab")).Click(); s.Driver.FindElement(By.Id("copy-tab")).Click();
@@ -1427,7 +1428,7 @@ namespace BTCPayServer.Tests
s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true); s.Driver.SetCheckbox(By.Id("DisableBolt11PaymentMethod"), true);
s.Driver.FindElement(By.Id("save")).Click(); s.Driver.FindElement(By.Id("save")).Click();
Assert.Contains($"{cryptoCode} Lightning settings successfully updated", s.FindAlertMessage().Text); 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.GoToInvoiceCheckout(invForPP);
s.Driver.FindElement(By.Id("copy-tab")).Click(); s.Driver.FindElement(By.Id("copy-tab")).Click();
lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value"); lnurl = s.Driver.FindElement(By.CssSelector("input.checkoutTextbox")).GetAttribute("value");
@@ -1486,25 +1487,19 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true); s.RegisterNewUser(true);
//ln address tests //ln address tests
s.CreateNewStore(); s.CreateNewStore();
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
//ensure ln address is not available as Lightning is not enable //ensure ln address is not available as Lightning is not enable
s.Driver.FindElement(By.Id("lightning-address-option")) s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
.FindElement(By.LinkText("You need to setup Lightning first"));
s.GoToStore(s.StoreId); s.GoToStore(s.StoreId);
s.AddLightningNode("BTC", LightningConnectionType.LndREST, false); s.AddLightningNode("BTC", LightningConnectionType.LndREST, false);
s.GoToStore(s.StoreId, StoreNavPages.Integrations);
//ensure ln address is not available as lnurl is not configured //ensure ln address is not available as lnurl is not configured
s.Driver.FindElement(By.Id("lightning-address-option")) s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
.FindElement(By.LinkText("You need LNURL configured first"));
s.GoToLightningSettings(s.StoreId, cryptoCode); s.GoToLightningSettings(s.StoreId, cryptoCode);
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode); SudoForceSaveLightningSettingsRightNowAndFast(s, cryptoCode);
s.GoToStore(s.StoreId, StoreNavPages.Integrations); s.Driver.FindElement(By.Id("StoreNav-LightningAddress")).Click();
s.Driver.FindElement(By.Id("lightning-address-option"))
.FindElement(By.Id("lightning-address-setup-link")).Click();
s.Driver.ToggleCollapse("AddAddress"); s.Driver.ToggleCollapse("AddAddress");
var lnaddress1 = Guid.NewGuid().ToString(); var lnaddress1 = Guid.NewGuid().ToString();

View File

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

View File

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

View File

@@ -23,59 +23,7 @@
<div class="accordion px-3 px-lg-4"> <div class="accordion px-3 px-lg-4">
@if (SignInManager.IsSignedIn(User)) @if (SignInManager.IsSignedIn(User))
{ {
@if (Model.Store == null) @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
{ {
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings"> <div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<div class="accordion-body"> <div class="accordion-body">
@@ -84,16 +32,16 @@
{ {
var isSetUp = !string.IsNullOrWhiteSpace(scheme.Value); var isSetUp = !string.IsNullOrWhiteSpace(scheme.Value);
<li class="nav-item"> <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 class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span> <span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
</a> </a>
} }
else 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 class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span> <span>@(Model.AltcoinsBuild ? $"{scheme.Crypto} " : "")Wallet</span>
</a> </a>
@@ -106,14 +54,14 @@
<li class="nav-item"> <li class="nav-item">
@if (isSetUp) @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 class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "disabled")"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span> <span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span>
</a> </a>
} }
else 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 class="me-2 btcpay-status btcpay-status--disabled"></span>
<span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span> <span>@(Model.AltcoinsBuild ? $"{scheme.CryptoCode} " : "")Lightning</span>
</a> </a>
@@ -134,19 +82,6 @@
<div id="Nav-Payments" class="accordion-collapse collapse show" aria-labelledby="Nav-Payments-Header"> <div id="Nav-Payments" class="accordion-collapse collapse show" aria-labelledby="Nav-Payments-Header">
<div class="accordion-body"> <div class="accordion-body">
<ul class="navbar-nav"> <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"> <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"> <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"/> <vc:icon symbol="invoice"/>
@@ -171,6 +106,12 @@
<span>Payouts</span> <span>Payouts</span>
</a> </a>
</li> </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> </ul>
</div> </div>
</div> </div>
@@ -204,6 +145,28 @@
</div> </div>
</div> </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"> <div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<header class="accordion-header" id="Nav-Manage-Header"> <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"> <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"> <div class="accordion-body">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <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"/> <vc:icon symbol="settings"/>
<span>Store Settings</span> <span>Store Settings</span>
</a> </a>
@@ -256,7 +219,7 @@
</div> </div>
<ul id="mainNavSettings" class="navbar-nav border-top p-3 px-lg-4"> <ul id="mainNavSettings" class="navbar-nav border-top p-3 px-lg-4">
<li class="nav-item" permission="@Policies.CanModifyServerSettings"> <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"/> <vc:icon symbol="server-settings"/>
<span>Server Settings</span> <span>Server Settings</span>
</a> </a>
@@ -264,7 +227,7 @@
<li class="nav-item"> <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"> <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"/> <vc:icon symbol="account"/>
<span>Account</span> <span class="text-truncate" style="max-width:195px">@User.Identity.Name</span>
</a> </a>
</li> </li>
@if (!theme.CustomTheme) @if (!theme.CustomTheme)

View File

@@ -7,7 +7,16 @@
<ul id="StoreSelectorMenu" class="dropdown-menu" aria-labelledby="StoreSelectorToggle"> <ul id="StoreSelectorMenu" class="dropdown-menu" aria-labelledby="StoreSelectorToggle">
@foreach (var option in Model.Options) @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><hr class="dropdown-divider"></li>
<li><a asp-controller="UserStores" asp-action="CreateStore" class="dropdown-item" id="StoreSelectorMenuItem-Create">Create Store</a></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.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using NBitcoin.Secp256k1;
namespace BTCPayServer.Components.StoreSelector namespace BTCPayServer.Components.StoreSelector
{ {
public class StoreSelector : ViewComponent public class StoreSelector : ViewComponent
{ {
private const string RootName = "Global";
private readonly StoreRepository _storeRepo; private readonly StoreRepository _storeRepo;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
public StoreSelector(StoreRepository storeRepo, UserManager<ApplicationUser> userManager) public StoreSelector(
StoreRepository storeRepo,
BTCPayNetworkProvider networkProvider,
UserManager<ApplicationUser> userManager)
{ {
_storeRepo = storeRepo; _storeRepo = storeRepo;
_userManager = userManager; _userManager = userManager;
_networkProvider = networkProvider;
} }
public async Task<IViewComponentResult> InvokeAsync() public async Task<IViewComponentResult> InvokeAsync()
@@ -27,11 +30,21 @@ namespace BTCPayServer.Components.StoreSelector
var stores = await _storeRepo.GetStoresByUserId(userId); var stores = await _storeRepo.GetStoresByUserId(userId);
var currentStore = ViewContext.HttpContext.GetStoreData(); var currentStore = ViewContext.HttpContext.GetStoreData();
var options = stores var options = stores
.Select(store => new SelectListItem .Select(store =>
{
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, Text = store.StoreName,
Value = store.Id, Value = store.Id,
Selected = store.Id == currentStore?.Id Selected = store.Id == currentStore?.Id,
WalletId = walletId
};
}) })
.ToList(); .ToList();
@@ -39,7 +52,7 @@ namespace BTCPayServer.Components.StoreSelector
{ {
Options = options, Options = options,
CurrentStoreId = currentStore?.Id, CurrentStoreId = currentStore?.Id,
CurrentDisplayName = currentStore?.StoreName ?? RootName CurrentDisplayName = currentStore?.StoreName
}; };
return View(vm); return View(vm);

View File

@@ -1,12 +1,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace BTCPayServer.Components.StoreSelector namespace BTCPayServer.Components.StoreSelector
{ {
public class StoreSelectorViewModel public class StoreSelectorViewModel
{ {
public List<SelectListItem> Options { get; set; } public List<StoreSelectorOption> Options { get; set; }
public string CurrentStoreId { get; set; } public string CurrentStoreId { get; set; }
public string CurrentDisplayName { 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.Security;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Stores;
using ExchangeSharp; using ExchangeSharp;
using Google.Apis.Auth.OAuth2; using Google.Apis.Auth.OAuth2;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -26,6 +27,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using NBitcoin; using NBitcoin;
using NBitcoin.Payment; using NBitcoin.Payment;
using NBitpayClient;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@@ -34,36 +36,51 @@ namespace BTCPayServer.Controllers
public class HomeController : Controller public class HomeController : Controller
{ {
private readonly ISettingsRepository _settingsRepository; private readonly ISettingsRepository _settingsRepository;
private readonly StoreRepository _storeRepository;
private readonly IFileProvider _fileProvider; private readonly IFileProvider _fileProvider;
private IHttpClientFactory HttpClientFactory { get; }
public IHttpClientFactory HttpClientFactory { get; } private SignInManager<ApplicationUser> SignInManager { get; }
public LanguageService LanguageService { get; } public LanguageService LanguageService { get; }
SignInManager<ApplicationUser> SignInManager { get; }
public HomeController(IHttpClientFactory httpClientFactory, public HomeController(IHttpClientFactory httpClientFactory,
ISettingsRepository settingsRepository, ISettingsRepository settingsRepository,
IWebHostEnvironment webHostEnvironment, IWebHostEnvironment webHostEnvironment,
LanguageService languageService, LanguageService languageService,
StoreRepository storeRepository,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager)
{ {
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
HttpClientFactory = httpClientFactory; HttpClientFactory = httpClientFactory;
LanguageService = languageService; LanguageService = languageService;
_storeRepository = storeRepository;
_fileProvider = webHostEnvironment.WebRootFileProvider; _fileProvider = webHostEnvironment.WebRootFileProvider;
SignInManager = signInManager; SignInManager = signInManager;
} }
[Route("")] [Route("")]
[DomainMappingConstraint()] [DomainMappingConstraint]
public async Task<IActionResult> Index() public async Task<IActionResult> Index()
{ {
if ((await _settingsRepository.GetTheme()).FirstRun) if ((await _settingsRepository.GetTheme()).FirstRun)
{ {
return RedirectToAction(nameof(AccountController.Register), "Account"); return RedirectToAction(nameof(AccountController.Register), "Account");
} }
if (SignInManager.IsSignedIn(User)) 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"); return View("Home");
else }
return Challenge(); return Challenge();
} }
@@ -71,7 +88,7 @@ namespace BTCPayServer.Controllers
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
public IActionResult Languages() 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)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
public IActionResult Permissions() 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")] [Route("swagger/v1/swagger.json")]
@@ -108,7 +125,7 @@ namespace BTCPayServer.Controllers
} }
[Route("recovery-seed-backup")] [Route("recovery-seed-backup")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
public IActionResult RecoverySeedBackup(RecoverySeedBackupViewModel vm) public IActionResult RecoverySeedBackup(RecoverySeedBackupViewModel vm)
{ {
return View("RecoverySeedBackup", vm); return View("RecoverySeedBackup", vm);

View File

@@ -740,29 +740,22 @@ namespace BTCPayServer.Controllers
[HttpGet("/stores/{storeId}/invoices")] [HttpGet("/stores/{storeId}/invoices")]
[HttpGet("invoices")] [HttpGet("invoices")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewInvoices)]
[BitpayAPIConstraint(false)] [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()); model = this.ParseListQuery(model ?? new InvoicesModel());
var fs = new SearchString(model.SearchTerm); 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() ? fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List<string>().ToArray()
: new []{ storeId }; : new []{ store.Id };
model.StoreIds = storeIds; 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 invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
invoiceQuery.StoreId = storeIds;
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery); var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
invoiceQuery.Take = model.Count; invoiceQuery.Take = model.Count;
invoiceQuery.Skip = model.Skip; invoiceQuery.Skip = model.Skip;

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,6 +34,7 @@ using NBitcoin.Payment;
using NBitpayClient; using NBitpayClient;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
using NBXplorer.Models; using NBXplorer.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo; using InvoiceCryptoInfo = BTCPayServer.Services.Invoices.InvoiceCryptoInfo;
@@ -410,6 +411,32 @@ namespace BTCPayServer
return false; 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) public static StoreData GetStoreData(this HttpContext ctx)
{ {
return ctx.Items.TryGet("BTCPAY.STOREDATA") as StoreData; return ctx.Items.TryGet("BTCPAY.STOREDATA") as StoreData;
@@ -418,12 +445,15 @@ namespace BTCPayServer
public static void SetStoreData(this HttpContext ctx, StoreData storeData) public static void SetStoreData(this HttpContext ctx, StoreData storeData)
{ {
ctx.Items["BTCPAY.STOREDATA"] = storeData; ctx.Items["BTCPAY.STOREDATA"] = storeData;
SetCurrentStoreId(ctx, storeData.Id);
} }
public static StoreData[] GetStoresData(this HttpContext ctx) public static StoreData[] GetStoresData(this HttpContext ctx)
{ {
return ctx.Items.TryGet("BTCPAY.STORESDATA") as StoreData[]; return ctx.Items.TryGet("BTCPAY.STORESDATA") as StoreData[];
} }
public static void SetStoresData(this HttpContext ctx, StoreData[] storeData) public static void SetStoresData(this HttpContext ctx, StoreData[] storeData)
{ {
ctx.Items["BTCPAY.STORESDATA"] = storeData; ctx.Items["BTCPAY.STORESDATA"] = storeData;

View File

@@ -10,7 +10,7 @@ using Newtonsoft.Json;
namespace BTCPayServer 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 class ControllerBaseExtension
{ {
public static T ParseListQuery<T>(this ControllerBase ctrl, T model) where T : BasePagingViewModel 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 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 // 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 // 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) if (searchTerm is null)
{ {
var section = prop.GetValue(prefCookie) as ListQueryDataHolder; 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.SearchTerm = section.SearchTerm;
model.TimezoneOffset = section.TimezoneOffset ?? 0; model.TimezoneOffset = section.TimezoneOffset ?? 0;
@@ -60,45 +60,5 @@ namespace BTCPayServer
return model; 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<IPaymentMethodHandler>(provider => provider.GetService<LightningLikePaymentHandler>());
services.AddSingleton<LNURLPayPaymentHandler>(); services.AddSingleton<LNURLPayPaymentHandler>();
services.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<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", services.AddSingleton<IUIExtension>(new UIExtension("LNURL/LightningAddressOption",
"store-integrations-list")); "store-integrations-list"));
services.AddSingleton<IHostedService, LightningListener>(); services.AddSingleton<IHostedService, LightningListener>();

View File

@@ -15,7 +15,9 @@ namespace BTCPayServer.Plugins.Shopify
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
applicationBuilder.AddSingleton<IHostedService, ShopifyOrderMarkerHostedService>(); 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")); "store-integrations-list"));
base.Execute(applicationBuilder); base.Execute(applicationBuilder);
} }

View File

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

View File

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

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))); 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) public static string GetImplicitStoreId(this HttpContext httpContext)
{ {
// 1. Check in the routeData // 1. Check in the routeData

View File

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

View File

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

View File

@@ -2,9 +2,7 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@model LNURLController.EditLightningAddressVM @model LNURLController.EditLightningAddressVM
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePage("LightningAddress", nameof(StoreNavPages), "Lightning Address", Context.GetStoreData().Id);
ViewData["NavPartialName"] = "../Stores/_Nav";
ViewData.SetActivePage(StoreNavPages.Integrations, "Lightning Address Setup", Context.GetStoreData().StoreName);
} }
@section PageHeadContent { @section PageHeadContent {
@@ -27,6 +25,8 @@
</script> </script>
} }
<partial name="_StatusMessage" />
@if (Context.Request.PathBase.ToString() != string.Empty) @if (Context.Request.PathBase.ToString() != string.Empty)
{ {
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
@@ -36,8 +36,8 @@
</div> </div>
} }
<div class="d-sm-flex align-items-center justify-content-between mb-2"> <div class="d-flex align-items-center justify-content-between mb-2">
<h2 class="mb-3 mb-sm-0">@ViewData["Title"]</h2> <h2 class="mb-0">@ViewData["Title"]</h2>
<a data-bs-toggle="collapse" data-bs-target="#AddAddress" class="btn btn-primary" role="button"> <a data-bs-toggle="collapse" data-bs-target="#AddAddress" class="btn btn-primary" role="button">
<span class="fa fa-plus"></span> <span class="fa fa-plus"></span>
Add Address Add Address
@@ -50,8 +50,8 @@
var showAdvancedOptions = !string.IsNullOrEmpty(Model.Add?.CurrencyCode) || Model.Add?.Min != null || Model.Add?.Max != null; 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="collapse @(showAddForm ? "show": "")" id="AddAddress">
<div class="form-group pt-3"> <div class="form-group pt-2">
<label asp-for="Add.Username" class="form-label"></label> <label asp-for="Add.Username" class="form-label"></label>
<div class="input-group"> <div class="input-group">
<input asp-for="Add.Username" class="form-control"/> <input asp-for="Add.Username" class="form-control"/>
@@ -59,7 +59,7 @@
</div> </div>
<span asp-validation-for="Add.Username" class="text-danger"></span> <span asp-validation-for="Add.Username" class="text-danger"></span>
</div> </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 id="AdvancedSettings" class="collapse @(showAdvancedOptions ? "show" : "")">
<div class="row"> <div class="row">
<div class="col-12 col-sm-auto"> <div class="col-12 col-sm-auto">
@@ -93,6 +93,8 @@
@if (Model.Items.Any()) @if (Model.Items.Any())
{ {
<div class="row">
<div class="col">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@@ -141,6 +143,8 @@
} }
</tbody> </tbody>
</table> </table>
</div>
</div>
} }
else else
{ {

View File

@@ -16,13 +16,11 @@
<span class="text-capitalize badge bg-secondary">@item.Amount @cryptoCode</span> <span class="text-capitalize badge bg-secondary">@item.Amount @cryptoCode</span>
</li> </li>
<form method="post" class="list-group-item justify-content-center" id="pay-invoices-form"> <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="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> <button type="button" class="btn btn-secondary mx-2" onclick="history.back(); return false;" style="min-width:25%;">Go back</button>
</form> </form>
} }
</ul> </ul>
</div> </div>
</div> </div>
@@ -34,7 +32,6 @@
$("#pay-invoices-form").on("submit", function() { $("#pay-invoices-form").on("submit", function() {
$(this).find("input[type='submit']").prop('disabled', true); $(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 // Give it a timeout to make sure all form validation has completed by the time we run our callback
setTimeout(function() { setTimeout(function() {
@@ -47,4 +44,3 @@
}); });
</script> </script>
} }
</section>

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
@model BTCPayServer.Models.PaymentRequestViewModels.UpdatePaymentRequestViewModel @model BTCPayServer.Models.PaymentRequestViewModels.UpdatePaymentRequestViewModel
@addTagHelper *, BundlerMinifier.TagHelpers @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 { @section PageHeadContent {

View File

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

View File

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

View File

@@ -2,20 +2,16 @@
@if (!string.IsNullOrEmpty(Model.ProvidedComment)) @if (!string.IsNullOrEmpty(Model.ProvidedComment))
{ {
<tr> <tr>
<td colspan="100% bg-tile"> <td colspan="100% bg-tile">
LNURL Comment: @Model.ProvidedComment LNURL Comment: @Model.ProvidedComment
</td> </td>
</tr> </tr>
} }
@if (!string.IsNullOrEmpty(Model.ConsumedLightningAddress)) @if (!string.IsNullOrEmpty(Model.ConsumedLightningAddress))
{ {
<tr> <tr>
<td colspan="100% bg-tile"> <td colspan="100% bg-tile">
Lightning address used: @Model.ConsumedLightningAddress Lightning address used: @Model.ConsumedLightningAddress
</td> </td>
</tr> </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') $text.removeAttribute('hidden')
$continue.setAttribute('disabled', 'disabled') $continue.setAttribute('disabled', 'disabled')
$inputText.textContent = confirmInput $inputText.textContent = confirmInput
$input.setAttribute("autocomplete", "off");
$input.addEventListener('input', event => { $input.addEventListener('input', event => {
event.target.value.trim() === confirmInput event.target.value.trim() === confirmInput
? $continue.removeAttribute('disabled') ? $continue.removeAttribute('disabled')

View File

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

View File

@@ -2,23 +2,23 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Plugins.Shopify.Models.ShopifySettings @model BTCPayServer.Plugins.Shopify.Models.ShopifySettings
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePage("shopify", nameof(StoreNavPages), "Shopify", Context.GetStoreData().Id);
ViewData["NavPartialName"] = "../Stores/_Nav";
ViewData.SetActivePage(StoreNavPages.Integrations, "Integrations");
var shopifyCredsSet = Model?.IntegratedAt.HasValue is true; var shopifyCredsSet = Model?.IntegratedAt.HasValue is true;
var shopifyUrl = Model?.ShopifyUrl; var shopifyUrl = Model?.ShopifyUrl;
} }
<form method="post" id="shopifyForm">
<h4 class="mb-3"> <partial name="_StatusMessage"/>
Shopify
<h2 class="mt-1 mb-4">
@ViewData["Title"]
<small> <small>
<a href="https://docs.btcpayserver.org/Shopify" target="_blank" rel="noreferrer noopener"> <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> <span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
</a> </a>
</small> </small>
</h4> </h2>
<form method="post" id="shopifyForm">
@if (!shopifyCredsSet) @if (!shopifyCredsSet)
{ {
<p class="alert alert-info">Create a Shopify Private App with the permissions "Orders - Read and write"</p> <p class="alert alert-info">Create a Shopify Private App with the permissions "Orders - Read and write"</p>

View File

@@ -2,14 +2,14 @@
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@model BTCPayServer.Models.WalletViewModels.NewPullPaymentModel @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" /> <partial name="_StatusMessage" />
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h4 class="mb-3">@ViewData["Title"]</h4> <h2 class="mb-3">@ViewData["Title"]</h2>
<form method="post" <form method="post"
asp-route-walletId="@Context.GetRouteValue("walletId")" asp-route-walletId="@Context.GetRouteValue("walletId")"

View File

@@ -7,7 +7,7 @@
@inject IEnumerable<IPayoutHandler> PayoutHandlers; @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 ??= new Dictionary<string, object>();
Model.PaginationQuery.Add("pullPaymentId", Model.PullPaymentId); Model.PaginationQuery.Add("pullPaymentId", Model.PullPaymentId);
Model.PaginationQuery.Add("paymentMethodId", Model.PaymentMethodId); Model.PaginationQuery.Add("paymentMethodId", Model.PaymentMethodId);

View File

@@ -3,7 +3,7 @@
@using BTCPayServer.Client @using BTCPayServer.Client
@model BTCPayServer.Models.WalletViewModels.PullPaymentsModel @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"]; var nextStartDateSortOrder = (string)ViewData["NextStartSortOrder"];
string startDateSortOrder = null; string startDateSortOrder = null;
switch (nextStartDateSortOrder) switch (nextStartDateSortOrder)

View File

@@ -2,7 +2,7 @@
@model CheckoutAppearanceViewModel @model CheckoutAppearanceViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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"> <div class="row">

View File

@@ -1,7 +1,7 @@
@model CreateTokenViewModel @model CreateTokenViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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.HidePublicKey = ViewBag.HidePublicKey ?? false;
ViewBag.ShowStores = ViewBag.ShowStores ?? false; ViewBag.ShowStores = ViewBag.ShowStores ?? false;
} }

View File

@@ -1,7 +1,7 @@
@model BTCPayServer.Models.ServerViewModels.EmailsViewModel @model BTCPayServer.Models.ServerViewModels.EmailsViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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" /> <partial name="EmailsBody" model="Model" />

View File

@@ -1,7 +1,7 @@
@model GeneralSettingsViewModel @model GeneralSettingsViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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"> <div class="row">

View File

@@ -4,7 +4,7 @@
var isHotWallet = Model.Method == WalletSetupMethod.HotWallet; var isHotWallet = Model.Method == WalletSetupMethod.HotWallet;
var type = isHotWallet ? "Hot" : "Watch-Only"; var type = isHotWallet ? "Hot" : "Watch-Only";
Layout = "_LayoutWalletSetup"; 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.CanUseHotWallet), Model.CanUseHotWallet);
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport); ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
ViewData.Add(nameof(Model.SupportSegwit), Model.SupportSegwit); ViewData.Add(nameof(Model.SupportSegwit), Model.SupportSegwit);

View File

@@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
Layout = "_LayoutWalletSetup"; 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 { @section Navbar {

View File

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

View File

@@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
Layout = "_LayoutWalletSetup"; 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 { @section Navbar {

View File

@@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
Layout = "_LayoutWalletSetup"; 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 { @section Navbar {

View File

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

View File

@@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
Layout = "_LayoutWalletSetup"; 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 { @section Navbar {

View File

@@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
Layout = "_LayoutWalletSetup"; 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 { @section Navbar {

View File

@@ -3,7 +3,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
Layout = "_LayoutWalletSetup"; 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 { @section Navbar {

View File

@@ -1,7 +1,7 @@
@model IntegrationsViewModel @model IntegrationsViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.Integrations, "Integrations", Context.GetStoreData().StoreName); ViewData.SetActivePage(StoreNavPages.Integrations, "Integrations", Context.GetStoreData().Id);
} }
<div class="row"> <div class="row">
@@ -12,7 +12,7 @@
} }
<ul class="list-group mb-3"> <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> </ul>
<h4 class="mt-5 mb-3">Other Integrations</h4> <h4 class="mt-5 mb-3">Other Integrations</h4>

View File

@@ -2,7 +2,7 @@
@model LightningSettingsViewModel @model LightningSettingsViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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"> <div class="row">

View File

@@ -1,7 +1,7 @@
@model TokensViewModel @model TokensViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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) @if (Model.StoreNotConfigured)

View File

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

View File

@@ -1,8 +1,7 @@
@inject BTCPayServer.Security.ContentSecurityPolicies csp @inject BTCPayServer.Security.ContentSecurityPolicies csp
@model PayButtonViewModel @model PayButtonViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().Id);
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().StoreName);
csp.AllowUnsafeHashes("onBTCPayFormSubmit(event);return false"); csp.AllowUnsafeHashes("onBTCPayFormSubmit(event);return false");
} }
@@ -69,10 +68,13 @@
</script> </script>
} }
<partial name="_StatusMessage" />
<h2 class="mt-1 mb-4">@ViewData["Title"]</h2>
<div id="payButtonCtrl"> <div id="payButtonCtrl">
<div class="row"> <div class="row">
<div class="col-lg-7"> <div class="col-lg-7">
<h3 class="mb-3">@ViewData["Title"]</h3>
<div class="row"> <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> <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> <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().Id);
ViewData.SetActivePage(StoreNavPages.PayButton, "Pay Button", Context.GetStoreData().StoreName);
} }
<h2 class="mt-1 mb-4">@ViewData["Title"]</h2>
<div class="row"> <div class="row">
<div class="col-md-10"> <div class="col-md-10">
<h4 class="mb-3">@ViewData["Title"]</h4>
<p> <p>
To start using Pay Button, you need to enable this feature explicitly. 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). 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 System.Text.RegularExpressions
@using BTCPayServer.Lightning @using BTCPayServer.Lightning
@using BTCPayServer.Services
@model PaymentMethodsViewModel @model PaymentMethodsViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Wallets", Context.GetStoreData().StoreName); ViewData.SetActivePage(StoreNavPages.PaymentMethods, "Wallets", Context.GetStoreData().Id);
} }
<div class="row"> <div class="row">

View File

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

View File

@@ -1,7 +1,7 @@
@model PairingModel @model PairingModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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> <h3 class="mb-0">@ViewData["Title"]</h3>

View File

@@ -1,7 +1,7 @@
@model LightningNodeViewModel @model LightningNodeViewModel
@{ @{
Layout = "_LayoutWalletSetup.cshtml"; 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"> <header class="text-center">

View File

@@ -1,7 +1,7 @@
@model WalletSetupViewModel @model WalletSetupViewModel
@{ @{
Layout = "_LayoutWalletSetup"; 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> <h1 class="text-center">Let's get started</h1>

View File

@@ -1,7 +1,7 @@
@model BTCPayServer.Security.Bitpay.BitTokenEntity @model BTCPayServer.Security.Bitpay.BitTokenEntity
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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> <h3 class="mb-4">@ViewData["Title"]</h3>

View File

@@ -2,6 +2,6 @@ namespace BTCPayServer.Views.Stores
{ {
public enum StoreNavPages 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 @model StoreUsersViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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"> <div class="row">

View File

@@ -2,7 +2,7 @@
@using BTCPayServer.Client.Models; @using BTCPayServer.Client.Models;
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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"> <div class="row">

View File

@@ -4,7 +4,8 @@
@model WalletSettingsViewModel @model WalletSettingsViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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 { @section PageHeadContent {

View File

@@ -1,7 +1,7 @@
@model WebhooksViewModel @model WebhooksViewModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; 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"> <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.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.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.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.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> <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" /> <vc:ui-extension-point location="store-nav" model="@Model" />

View File

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

View File

@@ -69,13 +69,13 @@
} }
</td> </td>
<td style="text-align:right"> <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> <a asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@store.Id">Pull Payments</a>
@if (store.IsOwner) @if (store.IsOwner)
{ {
<span> - </span> <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="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> </td>
</tr> </tr>

View File

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

View File

@@ -1,8 +1,9 @@
@model WalletPSBTViewModel @model WalletPSBTViewModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml"; Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.PSBT, "Decode PSBT", Context.GetStoreData().StoreName); ViewData.SetActivePage(WalletsNavPages.PSBT, "Decode PSBT", walletId);
} }
@section PageHeadContent { @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> <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"> <div class="form-group">
<label asp-for="PSBT" class="form-label"></label> <label asp-for="PSBT" class="form-label"></label>
<textarea class="form-control" rows="5" asp-for="PSBT"></textarea> <textarea class="form-control" rows="5" asp-for="PSBT"></textarea>

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
@model RescanWalletModel @model RescanWalletModel
@{ @{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "../Shared/_NavLayout.cshtml"; 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> <h4 class="mb-3">@ViewData["Title"]</h4>

View File

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

View File

@@ -1,14 +1,15 @@
@model WalletSendVaultModel @model WalletSendVaultModel
@{ @{
var walletId = Context.GetRouteValue("walletId").ToString();
Layout = "_LayoutWizard"; Layout = "_LayoutWizard";
ViewData.SetActivePage(WalletsNavPages.Send, "Sign the transaction", Context.GetStoreData().StoreName); ViewData.SetActivePage(WalletsNavPages.Send, "Sign the transaction", walletId);
} }
@section Navbar { @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" /> <vc:icon symbol="back" />
</a> </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" /> <vc:icon symbol="close" />
</a> </a>
} }
@@ -26,7 +27,7 @@
</div> </div>
<div id="body" class="my-4"> <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" id="WalletId" asp-for="WalletId" />
<input type="hidden" asp-for="WebsocketPath" /> <input type="hidden" asp-for="WebsocketPath" />
<partial name="SigningContext" for="SigningContext" /> <partial name="SigningContext" for="SigningContext" />

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,25 @@
@inject SignInManager<ApplicationUser> SignInManager @using BTCPayServer.Views.Stores
@inject BTCPayNetworkProvider BtcPayNetworkProvider @using BTCPayServer.Client
@inject BTCPayNetworkProvider _btcPayNetworkProvider
@{ @{
var wallet = WalletId.Parse(Context.GetRouteValue("walletId").ToString()); var walletId = Context.GetRouteValue("walletId")?.ToString();
var network = BtcPayNetworkProvider.GetNetwork<BTCPayNetwork>(wallet.CryptoCode); 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"> <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) @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.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-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")" id="SectionNav-Rescan">Rescan</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) @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>
} }
<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"/> <vc:ui-extension-point location="wallet-nav" model="@Model"/>
</nav> </nav>

View File

@@ -2,6 +2,5 @@
@using BTCPayServer.Views @using BTCPayServer.Views
@using BTCPayServer.Views.Wallets @using BTCPayServer.Views.Wallets
@{ @{
ViewBag.CategoryTitle = "Wallets";
ViewData.SetActiveCategory(typeof(WalletsNavPages)); 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-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="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="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> </svg>

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

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