From 20a9472ee283b1a47b94ac56317afdc2ca62fd78 Mon Sep 17 00:00:00 2001
From: d11n
Date: Mon, 7 Feb 2022 10:37:45 +0100
Subject: [PATCH] Sticky headers (#3416)
* Make headers sticky
Closes #3344.
* Decrease headline margin bottom on mobile
* increases gap
* adds bottom padding
* Update BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml
* add "_blank" to view action
* Fix markup and tests
* Spacing updates
* Try test fix
* Re-add sticky account header and add test logs for timeout check
* Fix timeout issues
* Apply scroll padding on pages with sticky header
Co-authored-by: dstrukt
Co-authored-by: nicolas.dorier
---
BTCPayServer.Tests/ApiKeysTests.cs | 31 ++-
BTCPayServer.Tests/Extensions.cs | 25 +--
BTCPayServer.Tests/SeleniumTests.cs | 39 +++-
.../Views/UIApps/UpdateCrowdfund.cshtml | 52 ++---
.../Views/UIApps/UpdatePointOfSale.cshtml | 59 +++--
BTCPayServer/Views/UIManage/_Nav.cshtml | 32 ++-
BTCPayServer/Views/UIServer/_Nav.cshtml | 43 ++--
BTCPayServer/Views/UIStores/_Nav.cshtml | 34 +--
.../wwwroot/main/bootstrap/bootstrap.css | 210 +++++-------------
BTCPayServer/wwwroot/main/layout.css | 42 +++-
10 files changed, 276 insertions(+), 291 deletions(-)
diff --git a/BTCPayServer.Tests/ApiKeysTests.cs b/BTCPayServer.Tests/ApiKeysTests.cs
index c194b656f..88d92c32a 100644
--- a/BTCPayServer.Tests/ApiKeysTests.cs
+++ b/BTCPayServer.Tests/ApiKeysTests.cs
@@ -47,6 +47,7 @@ namespace BTCPayServer.Tests
s.GoToProfile(ManageNavPages.APIKeys);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
+ TestLogs.LogInformation("Checking admin permissions");
//not an admin, so this permission should not show
Assert.DoesNotContain("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
await user.MakeAdmin();
@@ -56,30 +57,34 @@ namespace BTCPayServer.Tests
s.GoToProfile(ManageNavPages.APIKeys);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
-
//server management should show now
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.SetCheckbox(By.Id("btcpay.user.canviewprofile"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
-
+ TestLogs.LogInformation("Checking super admin key");
+
//this api key has access to everything
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings, Policies.CanModifyStoreSettings, Policies.CanViewProfile);
-
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
+
+ TestLogs.LogInformation("Checking CanModifyServerSettings permissions");
+
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
Policies.CanModifyServerSettings);
-
-
+
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.Driver.FindElement(By.Id("Generate")).Click();
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
+
+ TestLogs.LogInformation("Checking CanModifyStoreSettings permissions");
+
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
Policies.CanModifyStoreSettings);
@@ -97,12 +102,18 @@ namespace BTCPayServer.Tests
option.Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
+
+ TestLogs.LogInformation("Checking CanModifyStoreSettings with StoreId permissions");
+
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
+
+ TestLogs.LogInformation("Checking no permissions");
+
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user);
await Assert.ThrowsAnyAsync(async () =>
@@ -110,6 +121,8 @@ namespace BTCPayServer.Tests
await TestApiAgainstAccessToken("incorrect key", $"{TestApiPath}/me/id",
tester.PayTester.HttpClient);
});
+
+ TestLogs.LogInformation("Checking authorize screen");
//let's test the authorized screen now
//options for authorize are:
@@ -157,6 +170,9 @@ namespace BTCPayServer.Tests
Assert.Equal(callbackUrl, s.Driver.Url);
accessToken = GetAccessTokenFromCallbackResult(s.Driver);
+
+ TestLogs.LogInformation("Checking authorized permissions");
+
await TestApiAgainstAccessToken(accessToken, tester, user,
(await apiKeyRepo.GetKey(accessToken)).GetBlob().Permissions);
@@ -191,7 +207,10 @@ namespace BTCPayServer.Tests
}
s.Driver.FindElement(By.Id("Generate")).Click();
var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
- var apikeydata = await TestApiAgainstAccessToken(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
+
+ TestLogs.LogInformation("Checking API key permissions");
+
+ var apikeydata = await TestApiAgainstAccessToken(allAPIKey, "api/v1/api-keys/current", tester.PayTester.HttpClient);
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
}
diff --git a/BTCPayServer.Tests/Extensions.cs b/BTCPayServer.Tests/Extensions.cs
index 3514ba6d4..60dfaa000 100644
--- a/BTCPayServer.Tests/Extensions.cs
+++ b/BTCPayServer.Tests/Extensions.cs
@@ -135,6 +135,15 @@ retry:
}
public static void WaitForAndClick(this IWebDriver driver, By selector)
{
+ // Try fast path
+ try
+ {
+ driver.FindElement(selector).Click();
+ return;
+ }
+ catch { }
+
+ // Sometimes, selenium complain, so we enter hack territory
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
wait.UntilJsIsReady();
@@ -158,22 +167,8 @@ retry:
public static void SetCheckbox(this IWebDriver driver, By selector, bool value)
{
var element = driver.FindElement(selector);
- if ((value && !element.Selected) || (!value && element.Selected))
- {
- try
- {
- driver.WaitForAndClick(selector);
- }
- catch (ElementClickInterceptedException)
- {
- element.SendKeys(" ");
- }
- }
-
if (value != element.Selected)
- {
- driver.SetCheckbox(selector, value);
- }
+ driver.WaitForAndClick(selector);
}
}
}
diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs
index 468b18a38..fb57d050f 100644
--- a/BTCPayServer.Tests/SeleniumTests.cs
+++ b/BTCPayServer.Tests/SeleniumTests.cs
@@ -585,6 +585,8 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("AppName")).SendKeys("PoS" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Point of Sale");
s.Driver.FindElement(By.Id("Create")).Click();
+ Assert.Contains("App successfully created", s.FindAlertMessage().Text);
+
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click();
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
s.Driver.FindElement(By.Id("SaveItemChanges")).Click();
@@ -595,9 +597,14 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Item list and cart");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
- s.Driver.FindElement(By.Id("ViewApp")).Click();
+ Assert.Contains("App updated", s.FindAlertMessage().Text);
- var posBaseUrl = s.Driver.Url.Replace("/Cart", "");
+ s.Driver.FindElement(By.Id("ViewApp")).Click();
+ var windows = s.Driver.WindowHandles;
+ Assert.Equal(2, windows.Count);
+ s.Driver.SwitchTo().Window(windows[1]);
+
+ var posBaseUrl = s.Driver.Url.Replace("/cart", "");
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
Assert.True(s.Driver.PageSource.Contains("Take my money"), "PoS not showing correct default view");
@@ -607,6 +614,9 @@ namespace BTCPayServer.Tests
s.Driver.Url = posBaseUrl + "/cart";
Assert.True(s.Driver.PageSource.Contains("Cart"), "Cart PoS not showing correct view");
+
+ s.Driver.Close();
+ s.Driver.SwitchTo().Window(windows[0]);
}
[Fact(Timeout = TestTimeout)]
@@ -622,14 +632,26 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("AppName")).SendKeys("CF" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
s.Driver.FindElement(By.Id("Create")).Click();
+ Assert.Contains("App successfully created", s.FindAlertMessage().Text);
+
s.Driver.FindElement(By.Id("Title")).SendKeys("Kukkstarter");
s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC");
+ s.Driver.FindElement(By.Id("TargetCurrency")).Clear();
s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY");
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
+ Assert.Contains("App updated", s.FindAlertMessage().Text);
+
s.Driver.FindElement(By.Id("ViewApp")).Click();
+ var windows = s.Driver.WindowHandles;
+ Assert.Equal(2, windows.Count);
+ s.Driver.SwitchTo().Window(windows[1]);
+
Assert.Equal("currently active!",
s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
+
+ s.Driver.Close();
+ s.Driver.SwitchTo().Window(windows[0]);
}
[Fact(Timeout = TestTimeout)]
@@ -1529,10 +1551,19 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("AppName")).SendKeys("CF" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
s.Driver.FindElement(By.Id("Create")).Click();
+ Assert.Contains("App successfully created", s.FindAlertMessage().Text);
+
s.Driver.FindElement(By.Id("Title")).SendKeys("Kukkstarter");
s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
+ Assert.Contains("App updated", s.FindAlertMessage().Text);
+
s.Driver.FindElement(By.Id("ViewApp")).Click();
+
+ var windows = s.Driver.WindowHandles;
+ Assert.Equal(2, windows.Count);
+ s.Driver.SwitchTo().Window(windows[1]);
+
s.Driver.FindElement(By.CssSelector("#crowdfund-body-contribution-container .perk")).Click();
s.Driver.FindElement(By.PartialLinkText("LNURL")).Click();
lnurl = s.Driver.FindElement(By.ClassName("lnurl"))
@@ -1540,7 +1571,8 @@ namespace BTCPayServer.Tests
LNURL.LNURL.Parse(lnurl, out tag);
-
+ s.Driver.Close();
+ s.Driver.SwitchTo().Window(windows[0]);
}
[Fact]
@@ -1552,7 +1584,6 @@ namespace BTCPayServer.Tests
s.Server.ActivateLightning();
await s.StartAsync();
await s.Server.EnsureChannelsSetup();
- var cryptoCode = "BTC";
s.RegisterNewUser(true);
//ln address tests
s.CreateNewStore();
diff --git a/BTCPayServer/Views/UIApps/UpdateCrowdfund.cshtml b/BTCPayServer/Views/UIApps/UpdateCrowdfund.cshtml
index 7686fe898..663a6acd9 100644
--- a/BTCPayServer/Views/UIApps/UpdateCrowdfund.cshtml
+++ b/BTCPayServer/Views/UIApps/UpdateCrowdfund.cshtml
@@ -14,11 +14,26 @@
}
-
-
-@ViewData["Title"]
-
Other Actions
diff --git a/BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml
index 1813c9c91..d2d820c4f 100644
--- a/BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml
+++ b/BTCPayServer/Views/UIApps/UpdatePointOfSale.cshtml
@@ -5,11 +5,23 @@
ViewData.SetActivePage(AppsNavPages.Update, "Update Point of Sale", Model.Id);
}
-
-
-@ViewData["Title"]
-
+
+
+
+
+
+
@@ -204,13 +216,11 @@
A POST
callback will be sent to notification with the following form will be sent to notificationUrl
once the enough is paid and once again once there is enough confirmations to the payment:
@Model.ExampleCallback
Never trust anything but id
, ignore the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:
-
-
- Send a GET
request to https://btcpay.example.com/invoices/{invoiceId}
with Content-Type: application/json; Authorization: Basic YourLegacyAPIkey"
, Legacy API key can be created with Access Tokens in Store settings
- Verify that the orderId
is from your backend, that the price
is correct and that status
is settled
- You can then ship your order
-
-
+
+ Send a GET
request to https://btcpay.example.com/invoices/{invoiceId}
with Content-Type: application/json; Authorization: Basic YourLegacyAPIkey"
, Legacy API key can be created with Access Tokens in Store settings
+ Verify that the orderId
is from your backend, that the price
is correct and that status
is settled
+ You can then ship your order
+
@@ -243,23 +253,6 @@
-
Other Actions
@@ -332,7 +325,7 @@
diff --git a/BTCPayServer/Views/UIServer/_Nav.cshtml b/BTCPayServer/Views/UIServer/_Nav.cshtml
index 5107d949e..719dad850 100644
--- a/BTCPayServer/Views/UIServer/_Nav.cshtml
+++ b/BTCPayServer/Views/UIServer/_Nav.cshtml
@@ -1,21 +1,28 @@
@using BTCPayServer.Configuration
@inject BTCPayServerOptions _btcPayServerOptions
-Server Settings
-
-
-
+
+
+
diff --git a/BTCPayServer/Views/UIStores/_Nav.cshtml b/BTCPayServer/Views/UIStores/_Nav.cshtml
index a9208bc6f..4c199c7c9 100644
--- a/BTCPayServer/Views/UIStores/_Nav.cshtml
+++ b/BTCPayServer/Views/UIStores/_Nav.cshtml
@@ -1,14 +1,22 @@
@using BTCPayServer.Client
-Store Settings
-
-
-
+
+
+
+
diff --git a/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css b/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css
index 15b2a7907..495a8dac5 100644
--- a/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css
+++ b/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css
@@ -10617,25 +10617,25 @@ fieldset:disabled .btn {
}
/* Scrollbar - first works on Firefox, rest on WebKit-based browsers */
* {
- --btcpay-scrollbar-width: .375rem;
- --btcpay-scrollbar-color: var(--btcpay-neutral-500);
- --btcpay-scrollbar-bg: transparent;
- scrollbar-width: var(--btcpay-scrollbar-width);
- scrollbar-color: var(--btcpay-scrollbar-color) var(--btcpay-scrollbar-bg);
+ --btcpay-scrollbar-width: .375rem;
+ --btcpay-scrollbar-color: var(--btcpay-neutral-500);
+ --btcpay-scrollbar-bg: transparent;
+ scrollbar-width: var(--btcpay-scrollbar-width);
+ scrollbar-color: var(--btcpay-scrollbar-color) var(--btcpay-scrollbar-bg);
}
*::-webkit-scrollbar {
- width: var(--btcpay-scrollbar-width);
+ width: var(--btcpay-scrollbar-width);
}
*::-webkit-scrollbar-track {
- background: var(--btcpay-scrollbar-bg);
+ background: var(--btcpay-scrollbar-bg);
}
*::-webkit-scrollbar-thumb {
- background-color: var(--btcpay-scrollbar-color);
- border-radius: 1rem;
- border: 1rem solid var(--btcpay-scrollbar-bg);
+ background-color: var(--btcpay-scrollbar-color);
+ border-radius: 1rem;
+ border: 1rem solid var(--btcpay-scrollbar-bg);
}
html {
@@ -10901,149 +10901,59 @@ input[type=number].hide-number-spin {
color: var(--btcpay-danger);
}
+/* Custom space utils, as Bootstrap has a different spacing scale for > M */
+.pt-l { padding-top: var(--btcpay-space-l); }
+.pt-xl { padding-top: var(--btcpay-space-xl); }
+.pb-l { padding-bottom: var(--btcpay-space-l); }
+.pb-xl { padding-bottom: var(--btcpay-space-xl); }
+.mt-l { margin-top: var(--btcpay-space-l); }
+.mt-xl { margin-top: var(--btcpay-space-xl); }
+.mb-l { margin-bottom: var(--btcpay-space-l); }
+.mb-xl { margin-bottom: var(--btcpay-space-xl); }
+
/* Negative margin utils (only include bare minimum) */
-
-.mt-n1 {
- margin-top: -0.25rem !important;
-}
-
-.mt-n2 {
- margin-top: -0.5rem !important;
-}
-
-.mt-n3 {
- margin-top: -1rem !important;
-}
-
-.mt-n4 {
- margin-top: -1.5rem !important;
-}
-
-.mt-n5 {
- margin-top: -3rem !important;
-}
-
-.me-n1 {
- margin-right: -0.25rem !important;
-}
-
-.me-n2 {
- margin-right: -0.5rem !important;
-}
-
-.me-n3 {
- margin-right: -1rem !important;
-}
-
-.me-n4 {
- margin-right: -1.5rem !important;
-}
-
-.me-n5 {
- margin-right: -3rem !important;
-}
-
-.mb-n1 {
- margin-bottom: -0.25rem !important;
-}
-
-.mb-n2 {
- margin-bottom: -0.5rem !important;
-}
-
-.mb-n3 {
- margin-bottom: -1rem !important;
-}
-
-.mb-n4 {
- margin-bottom: -1.5rem !important;
-}
-
-.mb-n5 {
- margin-bottom: -3rem !important;
-}
-
-.ms-n1 {
- margin-left: -0.25rem !important;
-}
-
-.ms-n2 {
- margin-left: -0.5rem !important;
-}
-
-.ms-n3 {
- margin-left: -1rem !important;
-}
-
-.ms-n4 {
- margin-left: -1.5rem !important;
-}
-
-.ms-n5 {
- margin-left: -3rem !important;
-}
+.mt-n1 { margin-top: -0.25rem !important; }
+.mt-n2 { margin-top: -0.5rem !important; }
+.mt-n3 { margin-top: -1rem !important; }
+.mt-n4 { margin-top: -1.5rem !important; }
+.mt-n5 { margin-top: -3rem !important; }
+.me-n1 { margin-right: -0.25rem !important; }
+.me-n2 { margin-right: -0.5rem !important; }
+.me-n3 { margin-right: -1rem !important; }
+.me-n4 { margin-right: -1.5rem !important; }
+.me-n5 { margin-right: -3rem !important; }
+.mb-n1 { margin-bottom: -0.25rem !important; }
+.mb-n2 { margin-bottom: -0.5rem !important; }
+.mb-n3 { margin-bottom: -1rem !important; }
+.mb-n4 { margin-bottom: -1.5rem !important; }
+.mb-n5 { margin-bottom: -3rem !important; }
+.ms-n1 { margin-left: -0.25rem !important; }
+.ms-n2 { margin-left: -0.5rem !important; }
+.ms-n3 { margin-left: -1rem !important; }
+.ms-n4 { margin-left: -1.5rem !important; }
+.ms-n5 { margin-left: -3rem !important; }
@media (min-width: 992px) {
- .mt-lg-n1 {
- margin-top: -0.25rem !important;
- }
- .mt-lg-n2 {
- margin-top: -0.5rem !important;
- }
- .mt-lg-n3 {
- margin-top: -1rem !important;
- }
- .mt-lg-n4 {
- margin-top: -1.5rem !important;
- }
- .mt-lg-n5 {
- margin-top: -3rem !important;
- }
- .me-lg-n1 {
- margin-right: -0.25rem !important;
- }
- .me-lg-n2 {
- margin-right: -0.5rem !important;
- }
- .me-lg-n3 {
- margin-right: -1rem !important;
- }
- .me-lg-n4 {
- margin-right: -1.5rem !important;
- }
- .me-lg-n5 {
- margin-right: -3rem !important;
- }
- .mb-lg-n1 {
- margin-bottom: -0.25rem !important;
- }
- .mb-lg-n2 {
- margin-bottom: -0.5rem !important;
- }
- .mb-lg-n3 {
- margin-bottom: -1rem !important;
- }
- .mb-lg-n4 {
- margin-bottom: -1.5rem !important;
- }
- .mb-lg-n5 {
- margin-bottom: -3rem !important;
- }
- .ms-lg-n1 {
- margin-left: -0.25rem !important;
- }
- .ms-lg-n2 {
- margin-left: -0.5rem !important;
- }
- .ms-lg-n3 {
- margin-left: -1rem !important;
- }
- .ms-lg-n4 {
- margin-left: -1.5rem !important;
- }
- .ms-lg-n5 {
- margin-left: -3rem !important;
- }
+ .mt-lg-n1 { margin-top: -0.25rem !important; }
+ .mt-lg-n2 { margin-top: -0.5rem !important; }
+ .mt-lg-n3 { margin-top: -1rem !important; }
+ .mt-lg-n4 { margin-top: -1.5rem !important; }
+ .mt-lg-n5 { margin-top: -3rem !important; }
+ .me-lg-n1 { margin-right: -0.25rem !important; }
+ .me-lg-n2 { margin-right: -0.5rem !important; }
+ .me-lg-n3 { margin-right: -1rem !important; }
+ .me-lg-n4 { margin-right: -1.5rem !important; }
+ .me-lg-n5 { margin-right: -3rem !important; }
+ .mb-lg-n1 { margin-bottom: -0.25rem !important; }
+ .mb-lg-n2 { margin-bottom: -0.5rem !important; }
+ .mb-lg-n3 { margin-bottom: -1rem !important; }
+ .mb-lg-n4 { margin-bottom: -1.5rem !important; }
+ .mb-lg-n5 { margin-bottom: -3rem !important; }
+ .ms-lg-n1 { margin-left: -0.25rem !important; }
+ .ms-lg-n2 { margin-left: -0.5rem !important; }
+ .ms-lg-n3 { margin-left: -1rem !important; }
+ .ms-lg-n4 { margin-left: -1.5rem !important; }
+ .ms-lg-n5 { margin-left: -3rem !important; }
}
.btcpay-pills input {
diff --git a/BTCPayServer/wwwroot/main/layout.css b/BTCPayServer/wwwroot/main/layout.css
index 8b3e348de..fefe24b46 100644
--- a/BTCPayServer/wwwroot/main/layout.css
+++ b/BTCPayServer/wwwroot/main/layout.css
@@ -120,8 +120,8 @@
}
#mainContent > section {
- padding: 0;
flex: 1;
+ padding: var(--content-padding-top) var(--content-padding-horizontal) var(--content-padding-bottom);
}
#StoreSelector {
@@ -335,6 +335,28 @@
background: var(--btcpay-nav-bg-active);
}
+/* Sticky Header: The needs to be included once
+ before the first sticky-header on the page. The sticky-header has a padding-top so
+ that it does not scroll underneath the fixed header on mobile. The sticky-header-setup
+ negates that padding with a negative margin, so that everything fits in the end. */
+.sticky-header-setup {
+ margin-top: calc(var(--content-padding-top) * -1);
+}
+
+.sticky-header {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 0;
+ z-index: 1020;
+ background: var(--btcpay-body-bg);
+ padding-top: var(--content-padding-top);
+ padding-bottom: var(--btcpay-space-l);
+}
+
+.sticky-header #SectionNav {
+ margin-bottom: calc(var(--btcpay-space-l) * -1);
+}
+
/* Footer */
.btcpay-footer {
font-size: var(--btcpay-font-size-s);
@@ -357,10 +379,12 @@
@media (max-width: 991px) {
:root {
--header-height: var(--mobile-header-height);
- --content-padding-top: var(--btcpay-space-m);
+ --content-padding-top: calc(var(--header-height) + var(--btcpay-space-m));
+ --content-padding-bottom: var(--btcpay-space-xl);
+ --content-padding-horizontal: var(--btcpay-space-m);
/* Prevent anchors from disappearing underneath the fixed header */
- scroll-padding: calc(var(--header-height) + var(--content-padding-top));
+ scroll-padding: var(--content-padding-top);
}
#mainMenu {
@@ -471,11 +495,6 @@
z-index: 0;
}
- #mainContent > section {
- margin-top: var(--header-height);
- padding: var(--content-padding-top) var(--btcpay-space-m) var(--btcpay-space-xl);
- }
-
#SectionNav {
--scroll-indicator-spacing: var(--btcpay-space-m);
position: relative;
@@ -526,6 +545,9 @@
@media (min-width: 992px) {
:root {
--header-height: var(--desktop-header-height);
+ --content-padding-top: 5rem;
+ --content-padding-bottom: 5rem;
+ --content-padding-horizontal: var(--btcpay-space-xl);
}
#mainMenu {
@@ -575,10 +597,6 @@
max-width: calc(100vw - var(--sidebar-width) - (2 * var(--btcpay-space-xl)) - 1rem); /* 1rem for scrollbar */
}
- #mainContent > section {
- padding: 5rem var(--btcpay-space-xl);
- }
-
#mainContent > section,
.btcpay-footer > .container {
margin: 0;