Containerize store settings views into sections & clean up views (#7448)
What changed, and why it matters
This commit is a user-interface redesign, not a security fix. It wraps store settings pages in new visual 'section' containers, updates headings and spacing, and adjusts CSS styling. No code behavior, permissions, or data handling changed.
No security action needed. Treat as a normal UI/UX refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors Razor views and CSS for store settings pages. It introduces .settings-section/.settings-section__heading wrappers, moves subtitles into sticky headers, and tweaks margins. There are no controller, model, validation, authorization, or backend logic changes. The only functional-looking change is the removal of asp-selected=”@Model.Permissions.Contains(policy)” in CreateOrEditRole.cshtml, but the surrounding list-group already handles selection rendering via RenderTree, so this is a cleanup, not a security change.
Changed components
BTCPayServer Razor views for store settingsBTCPayServer main CSS files (site.css, site.rtl.css)Inspect captured patch +715 / −549
diff --git a/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml b/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml
index c383811..8f7728f 100644
--- a/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml
+++ b/BTCPayServer/Plugins/Bitpay/Views/CreateToken.cshtml
@@ -17,6 +17,7 @@
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xxl-constrain col-xl-8">
+ <div class="settings-section">
<div class="form-group">
<label asp-for="Label" class="form-label"></label>
@if (ViewBag.HidePublicKey)
@@ -28,7 +29,7 @@
</div>
@if (!ViewBag.HidePublicKey)
{
- <div class="form-group">
+ <div class="form-group@(((bool)ViewBag.ShowStores) ? "" : " mb-0")">
<label asp-for="PublicKey" class="form-label"></label>
<input asp-for="PublicKey" class="form-control" />
<span asp-validation-for="PublicKey" class="text-danger"></span>
@@ -37,7 +38,7 @@
}
@if (ViewBag.ShowStores)
{
- <div class="form-group">
+ <div class="form-group mb-0">
<label asp-for="Stores" class="form-label"></label>
<select asp-for="StoreId" asp-items="Model.Stores" class="form-select"></select>
<span asp-validation-for="StoreId" class="text-danger"></span>
@@ -47,6 +48,7 @@
{
<input type="hidden" asp-for="StoreId" />
}
+ </div>
</div>
</div>
</form>
diff --git a/BTCPayServer/Plugins/Bitpay/Views/ListTokens.cshtml b/BTCPayServer/Plugins/Bitpay/Views/ListTokens.cshtml
index 3c79977..438c203 100644
--- a/BTCPayServer/Plugins/Bitpay/Views/ListTokens.cshtml
+++ b/BTCPayServer/Plugins/Bitpay/Views/ListTokens.cshtml
@@ -16,28 +16,30 @@
</div>
}
<div class="sticky-header">
- <h2 class="my-1">
- <span text-translate="true">Greenfield API Keys</span>
- <a href="/docs" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- </h2>
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">
+ <span text-translate="true">Greenfield API Keys</span>
+ <a href="/docs" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ </h2>
+ <p class="text-secondary mb-0">
+ <span text-translate="true">To generate Greenfield API keys, please</span>
+ <a asp-controller="UIManage" asp-action="APIKeys" text-translate="true">click here</a>.
+ </p>
+ </div>
</div>
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xxl-constrain col-xl-8">
- <p>
- <span text-translate="true">To generate Greenfield API keys, please</span>
- <a asp-controller="UIManage" asp-action="APIKeys" text-translate="true">click here</a>.
- </p>
-
- <div class="d-flex align-items-center justify-content-between mt-5 mb-3">
+ <div class="settings-section__heading d-flex align-items-center justify-content-between">
<h3 class="mb-0">@ViewData["Title"]</h3>
<a id="CreateNewToken" asp-action="CreateToken" class="btn btn-primary" role="button" asp-route-storeId="@Context.GetRouteValue("storeId")" permission="@Policies.CanModifyStoreSettings" text-translate="true">
Create Token
</a>
</div>
+ <div class="settings-section">
<p>
<span text-translate="true">Authorize a public key to access Bitpay compatible Invoice API.</span>
<a href="https://support.bitpay.com/hc/en-us/articles/115003001183-How-do-I-pair-my-client-and-create-a-token-" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
@@ -72,17 +74,19 @@
}
else
{
- <p class="text-secondary mt-3" text-translate="true">
+ <p class="text-secondary mb-0" text-translate="true">
No access tokens yet.
</p>
}
+ </div>
- <h3 class="mt-5 mb-3" text-translate="true">Legacy API Keys</h3>
+ <h3 class="settings-section__heading settings-section__heading--spaced" text-translate="true">Legacy API Keys</h3>
+ <div class="settings-section">
<p text-translate="true">Alternatively, you can use the invoice API by including the following HTTP Header in your requests:</p>
<p><code>Authorization: Basic @Model.EncodedApiKey</code></p>
<form method="post" asp-action="GenerateAPIKey" asp-route-storeId="@Context.GetRouteValue("storeId")" permission="@Policies.CanModifyStoreSettings">
- <div class="form-group">
+ <div class="form-group mb-0">
<label asp-for="ApiKey" class="form-label"></label>
<div class="d-flex">
<input asp-for="ApiKey" readonly class="form-control"/>
@@ -98,6 +102,7 @@
</div>
</div>
</form>
+ </div>
</div>
</div>
diff --git a/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml b/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml
index 6442eb1..9ee850d 100644
--- a/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml
+++ b/BTCPayServer/Plugins/Bitpay/Views/RequestPairing.cshtml
@@ -27,7 +27,8 @@
<partial name="_StatusMessage" />
<div class="row">
<div class="col-sm-8 col-md-6 @(store is null ? "mx-auto" : "")">
- <table class="table table-hover">
+ <div class="settings-section">
+ <table class="table table-hover mt-0 mb-3">
<tr>
<th text-translate="true">Label</th>
<td class="text-end">@Model.Label</td>
@@ -39,7 +40,7 @@
</table>
@if (store is null)
{
- <div class="form-group">
+ <div class="form-group mb-0">
<label asp-for="StoreId" class="form-label" text-translate="true">Pair To Store</label>
<select asp-for="StoreId" asp-items="@(new SelectList(Model.Stores, "Id", "Name"))" class="form-select"></select>
<span asp-validation-for="StoreId" class="text-danger"></span>
@@ -50,6 +51,7 @@
<input asp-for="StoreId" type="hidden" />
}
<input type="hidden" name="pairingCode" value="@Model.Id"/>
+ </div>
</div>
</div>
</form>
diff --git a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesList.cshtml b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesList.cshtml
index 06f6e8d..6ec48b9 100644
--- a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesList.cshtml
+++ b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesList.cshtml
@@ -17,8 +17,31 @@
ViewData.SetBreadcrumbs([new(StringLocalizer["Emails"], Action: nameof(BTCPayServer.Plugins.Emails.Controllers.UIStoresEmailController.StoreEmailSettings), Controller: nameof(BTCPayServer.Plugins.Emails.Controllers.UIStoresEmailController), RouteValues: new { storeId })]);
}
}
-<div class="sticky-header">
- <vc:title-header />
+<div class="sticky-header align-items-start">
+ <nav aria-label="breadcrumb" class="flex-grow-1">
+ <ol class="breadcrumb">
+ <li class="breadcrumb-item">
+ @if (storeId is not null)
+ {
+ <a asp-controller="UIStoresEmail" asp-action="StoreEmailSettings" asp-route-storeId="@storeId" text-translate="true">Emails</a>
+ }
+ else
+ {
+ <a asp-controller="UIServerEmail" asp-action="ServerEmailSettings" text-translate="true">Server Emails</a>
+ }
+ </li>
+ <li class="breadcrumb-item active" aria-current="page">@ViewData.GetTitle()</li>
+ </ol>
+ <h2 class="mb-0">@ViewData.GetTitle()</h2>
+ @if (storeId is not null)
+ {
+ <p class="text-secondary mb-0 mt-1" text-translate="true">Email rules allow BTCPay Server to send customized emails from your store based on events.</p>
+ }
+ else
+ {
+ <p class="text-secondary mb-0 mt-1" text-translate="true">Email rules allow BTCPay Server to send customized emails from your server based on events.</p>
+ }
+ </nav>
@if (storeId is not null)
{
<a id="CreateEmailRule" permission="@Model.ModifyPermission" asp-action="StoreEmailRulesCreate" asp-route-storeId="@storeId"
@@ -36,23 +59,14 @@
</div>
<partial name="_StatusMessage" />
-@if (storeId is not null)
-{
- <p text-translate="true">
- Email rules allow BTCPay Server to send customized emails from your store based on events.
- </p>
-}
-else
-{
- <p text-translate="true">
- Email rules allow BTCPay Server to send customized emails from your server based on events.
- </p>
-}
-@if (Model.Rules.Any())
-{
- <div class="table-responsive">
- <table class="table table-hover">
+<div class="row">
+ <div class="col-xxl-constrain col-xl-8">
+ <div class="settings-section">
+ @if (Model.Rules.Any())
+ {
+ <div class="table-responsive settings-section__table">
+ <table class="table table-hover mb-0">
<thead>
<tr>
<th text-translate="true">Trigger</th>
@@ -96,12 +110,15 @@ else
</tbody>
</table>
</div>
-}
-else
-{
- <p class="text-secondary" text-translate="true">
- There are no rules yet.
- </p>
-}
+ }
+ else
+ {
+ <p class="text-secondary mb-0" text-translate="true">
+ There are no rules yet.
+ </p>
+ }
+ </div>
+ </div>
+</div>
<partial name="_Confirm" model="@(new ConfirmModel(StringLocalizer["Remove email rule"], StringLocalizer["This action will remove this rule. Are you sure?"], StringLocalizer["Delete"]))" permission="@Model.ModifyPermission" />
diff --git a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml
index d674a9d..cef2b10 100644
--- a/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml
+++ b/BTCPayServer/Plugins/Emails/Views/Shared/EmailRulesManage.cshtml
@@ -38,6 +38,9 @@
</div>
<partial name="_StatusMessage" />
+ <div class="row">
+ <div class="col-xxl-constrain col-xl-8">
+ <div class="settings-section">
<input type="hidden" asp-for="OfferingId"></input>
<input type="hidden" asp-for="RedirectUrl"></input>
<div class="form-group">
@@ -106,7 +109,7 @@
<span asp-validation-for="Subject" class="text-danger"></span>
</div>
- <div class="form-group">
+ <div class="form-group mb-0">
<label asp-for="Body" class="form-label" data-required></label>
<textarea asp-for="Body" class="form-control richtext email-rule-body" rows="4"></textarea>
<span asp-validation-for="Body" class="text-danger"></span>
@@ -116,6 +119,9 @@
</table>
<span>* These fields are JSON objects. You can access properties within them using <a href="https://www.newtonsoft.com/json/help/html/SelectToken.htm#SelectTokenJSONPath" rel="noreferrer noopener" target="_blank">this syntax</a>. For example <code>{Invoice.Metadata.itemCode}</code> or <code>{Subscriber.Metadata.accessKey}</code></span>
</div>
+ </div>
+ </div>
+ </div>
</div>
</form>
diff --git a/BTCPayServer/Plugins/Emails/Views/Shared/EmailSettings.cshtml b/BTCPayServer/Plugins/Emails/Views/Shared/EmailSettings.cshtml
index 548453c..d389162 100644
--- a/BTCPayServer/Plugins/Emails/Views/Shared/EmailSettings.cshtml
+++ b/BTCPayServer/Plugins/Emails/Views/Shared/EmailSettings.cshtml
@@ -10,11 +10,17 @@
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Emails), StringLocalizer["Email Rules"])
.SetCategory(WellKnownCategories.Store));
}
+ // Containerized section styling is applied on the store settings page only.
+ var sectionClass = Model.StoreId is not null ? "settings-section" : null;
+ var sectionHeadingClass = Model.StoreId is not null ? "settings-section__heading" : "my-3";
}
<form method="post" autocomplete="off">
- <div class="sticky-header">
- <h2>@ViewData.GetTitle()</h2>
+ <div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData.GetTitle()</h2>
+ <p class="text-secondary mb-0" text-translate="true">Configure the email server used to send emails from BTCPay Server.</p>
+ </div>
<div class="d-flex justify-content-end">
<button cheat-mode="true" id="mailpit" type="submit" class="btn btn-info" name="command" value="mailpit">Use mailpit</button>
@if (Model.StoreId is null)
@@ -40,6 +46,7 @@
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xl-10 col-xxl-constrain">
+ <div class="@sectionClass">
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All"></div>
@@ -134,7 +141,7 @@
}
</div>
<input asp-for="PasswordSet" type="hidden" />
- <div class="my-4">
+ <div>
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0" type="button" id="AdvancedSettingsButton"
data-bs-toggle="collapse" data-bs-target="#AdvancedSettings" aria-expanded="false" aria-controls="AdvancedSettings">
<vc:icon symbol="caret-down" />
@@ -151,17 +158,20 @@
</div>
</div>
</div>
+ </div>
</div>
</div>
- <h3 class="my-3" text-translate="true">Testing</h3>
+ <h3 class="@sectionHeadingClass @(Model.StoreId is not null ? "settings-section__heading--spaced" : "")" text-translate="true">Testing</h3>
<div class="row">
<div class="col-xl-10 col-xxl-constrain">
+ <div class="@sectionClass">
<div class="form-group">
<label asp-for="TestEmail" class="form-label" text-translate="true">To test your settings, enter an email address</label>
<input asp-for="TestEmail" placeholder="@StringLocalizer["Firstname Lastname <email@example.com>"]" class="form-control" />
<span asp-validation-for="TestEmail" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-secondary mt-2" name="command" value="Test" id="Test" text-translate="true">Send Test Email</button>
+ </div>
</div>
</div>
</form>
diff --git a/BTCPayServer/Plugins/Forms/Views/FormsList.cshtml b/BTCPayServer/Plugins/Forms/Views/FormsList.cshtml
index bcf8049..729da61 100644
--- a/BTCPayServer/Plugins/Forms/Views/FormsList.cshtml
+++ b/BTCPayServer/Plugins/Forms/Views/FormsList.cshtml
@@ -6,14 +6,17 @@
Layout = "_Layout";
}
-<div class="sticky-header">
- <h2>
- <span>@ViewData["Title"]</span>
- <a href="https://docs.btcpayserver.org/Forms" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info" />
- </a>
- </h2>
- <a id="page-primary" asp-action="Create" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button" permission="@Policies.CanModifyStoreSettings" text-translate="true">
+<div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">
+ <span>@ViewData["Title"]</span>
+ <a href="https://docs.btcpayserver.org/Forms" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info" />
+ </a>
+ </h2>
+ <p class="text-secondary mb-0" text-translate="true">Create reusable forms to collect extra information from customers during checkout.</p>
+ </div>
+ <a id="page-primary" asp-action="Create" asp-route-storeId="@storeId" class="btn btn-primary" role="button" permission="@Policies.CanModifyStoreSettings" text-translate="true">
Create Form
</a>
</div>
@@ -21,10 +24,11 @@
<div class="row">
<div class="col-xxl-constrain col-xl-10">
+ <div class="settings-section">
@if (Model.Any())
{
- <div class="table-responsive-md mt-0">
- <table class="table table-hover">
+ <div class="table-responsive-md settings-section__table">
+ <table class="table table-hover mb-0">
<thead>
<tr>
<th text-translate="true">Name</th>
@@ -51,10 +55,11 @@
}
else
{
- <p class="text-secondary" text-translate="true">
+ <p class="text-secondary mb-0" text-translate="true">
There are no forms yet.
</p>
}
+ </div>
</div>
</div>
diff --git a/BTCPayServer/Plugins/Webhooks/Views/ModifyWebhook.cshtml b/BTCPayServer/Plugins/Webhooks/Views/ModifyWebhook.cshtml
index d0c4f06..2b5ad26 100644
--- a/BTCPayServer/Plugins/Webhooks/Views/ModifyWebhook.cshtml
+++ b/BTCPayServer/Plugins/Webhooks/Views/ModifyWebhook.cshtml
@@ -28,52 +28,56 @@
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xxl-constrain col-xl-8">
- <div class="form-group">
- <label asp-for="PayloadUrl" class="form-label" text-translate="true">Payload URL</label>
- <input asp-for="PayloadUrl" class="form-control" />
- <span asp-validation-for="PayloadUrl" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="Secret" class="form-label"></label>
- <div class="input-group">
- <input asp-for="Secret" type="password" class="form-control" value="@Model.Secret">
- <button type="button" class="btn btn-secondary px-3 only-for-js" title="@StringLocalizer["Toggle password visibility"]" data-toggle-password="#Secret">
- <vc:icon symbol="actions-show" />
- </button>
+ <div class="settings-section">
+ <div class="form-group">
+ <label asp-for="PayloadUrl" class="form-label" text-translate="true">Payload URL</label>
+ <input asp-for="PayloadUrl" class="form-control" />
+ <span asp-validation-for="PayloadUrl" class="text-danger"></span>
</div>
- <div class="form-text" html-translate="true">The endpoint receiving the payload must validate the payload by checking that the HTTP header <code>BTCPAY-SIG</code> of the callback matches the HMAC256 of the secret on the payload's body bytes.</div>
- </div>
- <div class="form-group">
- <label class="form-group d-flex align-items-center">
- <input asp-for="AutomaticRedelivery" type="checkbox" class="btcpay-toggle me-3" />
- <div>
- <span text-translate="true">Automatic redelivery</span>
- <div class="form-text" text-translate="true">We will try to redeliver any failed delivery after 10 seconds, 1 minute and up to 6 times after 10 minutes</div>
+ <div class="form-group">
+ <label asp-for="Secret" class="form-label"></label>
+ <div class="input-group">
+ <input asp-for="Secret" type="password" class="form-control" value="@Model.Secret">
+ <button type="button" class="btn btn-secondary px-3 only-for-js" title="@StringLocalizer["Toggle password visibility"]" data-toggle-password="#Secret">
+ <vc:icon symbol="actions-show" />
+ </button>
</div>
- </label>
- </div>
- <div class="form-group">
- <label class="d-flex align-items-center mb-2">
- <input asp-for="Active" type="checkbox" class="btcpay-toggle me-3" />
- <span text-translate="true">Enabled</span>
- </label>
+ <div class="form-text" html-translate="true">The endpoint receiving the payload must validate the payload by checking that the HTTP header <code>BTCPAY-SIG</code> of the callback matches the HMAC256 of the secret on the payload's body bytes.</div>
+ </div>
+ <div class="form-group">
+ <label class="form-group d-flex align-items-center">
+ <input asp-for="AutomaticRedelivery" type="checkbox" class="btcpay-toggle me-3" />
+ <div>
+ <span text-translate="true">Automatic redelivery</span>
+ <div class="form-text" text-translate="true">We will try to redeliver any failed delivery after 10 seconds, 1 minute and up to 6 times after 10 minutes</div>
+ </div>
+ </label>
+ </div>
+ <div class="form-group mb-0">
+ <label class="d-flex align-items-center mb-2">
+ <input asp-for="Active" type="checkbox" class="btcpay-toggle me-3" />
+ <span text-translate="true">Enabled</span>
+ </label>
+ </div>
</div>
- <h3 class="mb-3 mt-5">Events</h3>
- <label asp-for="Everything" class="form-label" text-translate="true">Which events would you like to trigger this webhook?</label>
- <select asp-for="Everything" class="form-select w-auto mb-3">
- <option value="true" text-translate="true">Send me everything</option>
- <option value="false" text-translate="true">Send specific events</option>
- </select>
- <div id="event-selector" class="collapse">
- <div class="pb-3">
- @foreach (var evt in Webhooks)
- {
- <div class="form-check my-1">
- <input name="Events" id="@evt.Type" value="@evt.Type" @(Model.Events.Contains(evt.Type) ? "checked" : "") type="checkbox" class="form-check-input" />
- <label for="@evt.Type" class="form-check-label">@StringLocalizer[evt.Description]</label>
- </div>
- }
+ <h3 class="settings-section__heading settings-section__heading--spaced" text-translate="true">Events</h3>
+ <div class="settings-section">
+ <label asp-for="Everything" class="form-label" text-translate="true">Which events would you like to trigger this webhook?</label>
+ <select asp-for="Everything" class="form-select w-auto mb-3">
+ <option value="true" text-translate="true">Send me everything</option>
+ <option value="false" text-translate="true">Send specific events</option>
+ </select>
+ <div id="event-selector" class="collapse">
+ <div class="pb-0">
+ @foreach (var evt in Webhooks)
+ {
+ <div class="form-check my-1">
+ <input name="Events" id="@evt.Type" value="@evt.Type" @(Model.Events.Contains(evt.Type) ? "checked" : "") type="checkbox" class="form-check-input" />
+ <label for="@evt.Type" class="form-check-label">@StringLocalizer[evt.Description]</label>
+ </div>
+ }
+ </div>
</div>
</div>
</div>
@@ -83,8 +87,9 @@
<div class="col-xxl-constrain col-xl-8">
@if (!Model.IsNew && Model.Deliveries.Count > 0)
{
- <h4 class="mb-3" text-translate="true">Recent deliveries</h4>
- <ul class="list-group">
+ <h3 class="settings-section__heading settings-section__heading--spaced" text-translate="true">Recent deliveries</h3>
+ <div class="settings-section">
+ <ul class="list-group list-group-flush">
@foreach (var delivery in Model.Deliveries)
{
<li class="list-group-item ">
@@ -131,6 +136,7 @@
</li>
}
</ul>
+ </div>
}
</div>
</div>
diff --git a/BTCPayServer/Plugins/Webhooks/Views/Webhooks.cshtml b/BTCPayServer/Plugins/Webhooks/Views/Webhooks.cshtml
index f571047..77ab5a7 100644
--- a/BTCPayServer/Plugins/Webhooks/Views/Webhooks.cshtml
+++ b/BTCPayServer/Plugins/Webhooks/Views/Webhooks.cshtml
@@ -4,21 +4,24 @@
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Webhooks), StringLocalizer["Webhooks"]).SetCategory(WellKnownCategories.Store));
}
-<div class="sticky-header">
- <vc:title-header />
+<div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData["Title"]</h2>
+ <p class="text-secondary mb-0" text-translate="true">Webhooks allow BTCPay Server to send HTTP events related to your store to another server.</p>
+ </div>
<a id="page-primary" text-translate="true" asp-action="NewWebhook" class="btn btn-primary" role="button" asp-route-storeId="@Context.GetRouteValue("storeId")" permission="@Policies.CanModifyStoreSettings">
Create Webhook
</a>
</div>
<partial name="_StatusMessage" />
-<p text-translate="true">Webhooks allow BTCPay Server to send HTTP events related to your store to another server.</p>
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
+ <div class="settings-section">
@if (Model.Webhooks.Any())
{
- <div class="table-responsive-md">
- <table class="table table-hover">
+ <div class="table-responsive-md settings-section__table">
+ <table class="table table-hover mb-0">
<thead>
<tr>
<th text-translate="true">Status</th>
@@ -63,10 +66,11 @@
}
else
{
- <p class="text-secondary mt-3" text-translate="true">
+ <p class="text-secondary mb-0" text-translate="true">
There are no webhooks yet.
</p>
}
+ </div>
</div>
</div>
diff --git a/BTCPayServer/Views/Shared/CreateOrEditRole.cshtml b/BTCPayServer/Views/Shared/CreateOrEditRole.cshtml
index b7cc217..941fe57 100644
--- a/BTCPayServer/Views/Shared/CreateOrEditRole.cshtml
+++ b/BTCPayServer/Views/Shared/CreateOrEditRole.cshtml
@@ -33,37 +33,38 @@
{
<div asp-validation-summary="ModelOnly"></div>
}
- <div class="form-group" style="max-width:320px">
- <label asp-for="Role" class="form-label"></label>
- @if (role == null)
- {
- <input asp-for="Role" required="required" class="form-control" />
- }
- else
- {
- <input type="hidden" asp-for="Role" />
- <input required="required" class="form-control" disabled value="@Model.Role" />
- }
- <span asp-validation-for="Role" class="text-danger"></span>
- </div>
-
- <h4 class="mt-4 mb-3">Permissions</h4>
- <select multiple="multiple" asp-for="Permissions" class="form-select hide-when-js">
- @foreach (var policy in storePolicies)
- {
- <option value="@policy" class="text-truncate" asp-selected="@Model.Permissions.Contains(policy)">@policy</option>
- }
- </select>
- <div class="list-group mb-2">
- @{
- var topMostPolicies = PermissionService.UnrestrictedPermissionNode.Children.Where(pair => pair.Definition.Type is PolicyType.Store);
- @foreach (var policy in topMostPolicies)
+ <div class="settings-section">
+ <div class="form-group" style="max-width:320px">
+ <label asp-for="Role" class="form-label"></label>
+ @if (role == null)
{
- RenderTree(policy, Model.Permissions.Contains(policy.Definition.Policy));
+ <input asp-for="Role" required="required" class="form-control" />
}
- }
+ else
+ {
+ <input type="hidden" asp-for="Role" />
+ <input required="required" class="form-control" disabled value="@Model.Role" />
+ }
+ <span asp-validation-for="Role" class="text-danger"></span>
+ </div>
+ <h3 class="settings-section__subheading mt-2 mb-3" text-translate="true">Permissions</h3>
+ <select multiple="multiple" asp-for="Permissions" class="form-select hide-when-js">
+ @foreach (var policy in storePolicies)
+ {
+ <option value="@policy" class="text-truncate">@policy</option>
+ }
+ </select>
+ <div class="list-group mb-0">
+ @{
+ var topMostPolicies = PermissionService.UnrestrictedPermissionNode.Children.Where(pair => pair.Definition.Type is PolicyType.Store);
+ @foreach (var policy in topMostPolicies)
+ {
+ RenderTree(policy, Model.Permissions.Contains(policy.Definition.Policy));
+ }
+ }
+ </div>
+ <span asp-validation-for="Permissions" class="text-danger"></span>
</div>
- <span asp-validation-for="Permissions" class="text-danger"></span>
</div>
</div>
</form>
diff --git a/BTCPayServer/Views/Shared/ListRoles.cshtml b/BTCPayServer/Views/Shared/ListRoles.cshtml
index 74aa49e..bb8d812 100644
--- a/BTCPayServer/Views/Shared/ListRoles.cshtml
+++ b/BTCPayServer/Views/Shared/ListRoles.cshtml
@@ -24,14 +24,18 @@
var showInUseColumn = !Model.Roles.Any(r => r.IsUsed is null);
}
-<div class="sticky-header">
- <vc:title-header />
+<div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData["Title"]</h2>
+ <p class="text-secondary mb-0" text-translate="true">Roles define the permissions granted to store users.</p>
+ </div>
<a id="page-primary" text-translate="true" class="btn btn-primary" role="button" asp-controller="@(string.IsNullOrEmpty(storeId) ? "UIServer" : "UIStores")" asp-action="CreateOrEditRole" asp-route-role="create" asp-route-storeId="@storeId" permission="@permission">Add Role</a>
</div>
<partial name="_StatusMessage" />
-<div class="table-responsive">
- <table class="table table-hover">
+<div class="settings-section">
+<div class="table-responsive settings-section__table">
+ <table class="table table-hover mb-0">
<thead>
<tr>
<th>
@@ -128,3 +132,4 @@
</tbody>
</table>
</div>
+</div>
diff --git a/BTCPayServer/Views/UILightningAutomatedPayoutProcessors/Configure.cshtml b/BTCPayServer/Views/UILightningAutomatedPayoutProcessors/Configure.cshtml
index 71bc4d9..1dc9085 100644
--- a/BTCPayServer/Views/UILightningAutomatedPayoutProcessors/Configure.cshtml
+++ b/BTCPayServer/Views/UILightningAutomatedPayoutProcessors/Configure.cshtml
@@ -15,17 +15,18 @@
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
- <p text-translate="true">Set a schedule for automated Lightning Network Payouts.</p>
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All"></div>
}
- <div class="d-flex my-3">
+ <div class="settings-section">
+ <p text-translate="true">Set a schedule for automated Lightning Network Payouts.</p>
+ <div class="d-flex mb-3">
<input asp-for="ProcessNewPayoutsInstantly" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="ProcessNewPayoutsInstantly" class="form-check-label"></label>
<span asp-validation-for="ProcessNewPayoutsInstantly" class="text-danger"></span>
</div>
- <div class="form-group">
+ <div class="form-group mb-0">
<label asp-for="IntervalMinutes" class="form-label" data-required text-translate="true">Interval</label>
<div class="input-group">
<input asp-for="IntervalMinutes" class="form-control" inputmode="numeric" style="max-width:12ch;">
@@ -33,6 +34,7 @@
<span asp-validation-for="IntervalMinutes" class="text-danger"></span>
</div>
</div>
+ </div>
</div>
</div>
</form>
diff --git a/BTCPayServer/Views/UIOnChainAutomatedPayoutProcessors/Configure.cshtml b/BTCPayServer/Views/UIOnChainAutomatedPayoutProcessors/Configure.cshtml
index e81a0f8..a8ad547 100644
--- a/BTCPayServer/Views/UIOnChainAutomatedPayoutProcessors/Configure.cshtml
+++ b/BTCPayServer/Views/UIOnChainAutomatedPayoutProcessors/Configure.cshtml
@@ -16,12 +16,13 @@
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
- <p text-translate="true">Set a schedule for automated On-Chain Bitcoin Payouts.</p>
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All"></div>
}
- <div class="d-flex my-3">
+ <div class="settings-section">
+ <p text-translate="true">Set a schedule for automated On-Chain Bitcoin Payouts.</p>
+ <div class="d-flex mb-3">
<input asp-for="ProcessNewPayoutsInstantly" type="checkbox" class="btcpay-toggle me-3" />
<label asp-for="ProcessNewPayoutsInstantly" class="form-check-label"></label>
<span asp-validation-for="ProcessNewPayoutsInstantly" class="text-danger"></span>
@@ -42,7 +43,7 @@
<span asp-validation-for="FeeTargetBlock" class="text-danger"></span>
</div>
</div>
- <div class="form-group">
+ <div class="form-group mb-0">
<label asp-for="Threshold" class="form-label" data-required text-translate="true">Threshold</label>
<div class="input-group">
<input asp-for="Threshold" class="form-control" min="0" inputmode="numeric" style="max-width:12ch;">
@@ -51,6 +52,7 @@
</div>
<div class="form-text" text-translate="true">Only process payouts when this payout sum is reached.</div>
</div>
+ </div>
</div>
</div>
</form>
diff --git a/BTCPayServer/Views/UIPayoutProcessors/ConfigureStorePayoutProcessors.cshtml b/BTCPayServer/Views/UIPayoutProcessors/ConfigureStorePayoutProcessors.cshtml
index 2a21f4c..5d37884 100644
--- a/BTCPayServer/Views/UIPayoutProcessors/ConfigureStorePayoutProcessors.cshtml
+++ b/BTCPayServer/Views/UIPayoutProcessors/ConfigureStorePayoutProcessors.cshtml
@@ -6,21 +6,24 @@
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.PayoutProcessors), StringLocalizer["Payout Processors"]).SetCategory(WellKnownCategories.Store));
}
<div class="sticky-header">
- <h2 class="my-1">@ViewData["Title"]</h2>
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData["Title"]</h2>
+ <p class="text-secondary mb-0" text-translate="true">Payout Processors allow BTCPay Server to handle payouts in an automated way.</p>
+ </div>
</div>
<partial name="_StatusMessage" />
-<p text-translate="true">Payout Processors allow BTCPay Server to handle payouts in an automated way.</p>
-
<div class="row">
<div class="col-xl-8 col-xxl-constrain">
@if (Model.Any())
{
foreach (var processorsView in Model)
{
- <h4 class="mt-5">@processorsView.Factory.FriendlyName</h4>
- <table class="table table-hover mt-0">
+ <h3 class="settings-section__heading">@processorsView.Factory.FriendlyName</h3>
+ <div class="settings-section">
+ <div class="table-responsive settings-section__table">
+ <table class="table table-hover mb-0">
<thead>
<tr>
<th text-translate="true">Payment Method</th>
@@ -54,6 +57,8 @@
}
</tbody>
</table>
+ </div>
+ </div>
}
}
else
diff --git a/BTCPayServer/Views/UIStores/CheckoutAppearance.cshtml b/BTCPayServer/Views/UIStores/CheckoutAppearance.cshtml
index d2f89d0..de31560 100644
--- a/BTCPayServer/Views/UIStores/CheckoutAppearance.cshtml
+++ b/BTCPayServer/Views/UIStores/CheckoutAppearance.cshtml
@@ -61,8 +61,11 @@
}
<form method="post" enctype="multipart/form-data" permissioned="@Policies.CanModifyStoreSettings">
- <div class="sticky-header">
- <vc:title-header />
+ <div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData["Title"]</h2>
+ <p class="text-secondary mb-0" text-translate="true">Customize the checkout experience your customers see when paying.</p>
+ </div>
<button id="page-primary" type="submit" class="btn btn-primary" text-translate="true">Save</button>
</div>
<partial name="_StatusMessage" />
@@ -75,174 +78,184 @@
}
@if (Model.PaymentMethods.Any())
{
- <div class="form-group mb-4">
- <label asp-for="DefaultPaymentMethod" class="form-label"></label>
- <select asp-for="DefaultPaymentMethod" asp-items="Model.PaymentMethods" class="form-select w-auto"></select>
- </div>
- <div class="form-group mb-4">
- <div class="form-label mb-1" text-translate="true">Enable payment methods only when amount is …</div>
- <table class="table table-sm mt-0 mx-0">
- @for (var index = 0; index < Model.PaymentMethodCriteria.Count; index++)
- {
- var criteria = Model.PaymentMethodCriteria[index];
- <tr>
- <td class="border-0 ps-0 align-middle">
- <input type="hidden" asp-for="PaymentMethodCriteria[index].PaymentMethod" />
- @PaymentMethodId.Parse(criteria.PaymentMethod).ToString()
- </td>
- <td class="border-0">
- <select asp-for="PaymentMethodCriteria[index].Type" class="form-select"
- asp-items="@((PaymentMethodCriteriaViewModel.CriteriaType[]) Enum.GetValues(typeof(PaymentMethodCriteriaViewModel.CriteriaType))).Select(s => new SelectListItem(CriteriaTypeDisplayName(s), s.ToString()))">
- </select>
- </td>
- <td class="border-0">
- <input placeholder="6.15 USD" asp-for="PaymentMethodCriteria[index].Value" class="form-control my-0" style="max-width: 20ch;" />
- </td>
- </tr>
- }
- </table>
+ <h3 id="payment-methods" class="settings-section__heading" text-translate="true">Payment Methods</h3>
+ <div class="settings-section">
+ <div class="form-group">
+ <label asp-for="DefaultPaymentMethod" class="form-label"></label>
+ <select asp-for="DefaultPaymentMethod" asp-items="Model.PaymentMethods" class="form-select w-auto"></select>
+ </div>
+ <div class="form-group mb-0">
+ <div class="form-label mb-1" text-translate="true">Enable payment methods only when amount is …</div>
+ <div class="table-responsive">
+ <table class="table table-sm mt-0 mx-0">
+ @for (var index = 0; index < Model.PaymentMethodCriteria.Count; index++)
+ {
+ var criteria = Model.PaymentMethodCriteria[index];
+ <tr>
+ <td class="border-0 ps-0 align-middle">
+ <input type="hidden" asp-for="PaymentMethodCriteria[index].PaymentMethod" />
+ @PaymentMethodId.Parse(criteria.PaymentMethod).ToString()
+ </td>
+ <td class="border-0">
+ <select asp-for="PaymentMethodCriteria[index].Type" class="form-select"
+ asp-items="@((PaymentMethodCriteriaViewModel.CriteriaType[]) Enum.GetValues(typeof(PaymentMethodCriteriaViewModel.CriteriaType))).Select(s => new SelectListItem(CriteriaTypeDisplayName(s), s.ToString()))">
+ </select>
+ </td>
+ <td class="border-0">
+ <input placeholder="6.15 USD" asp-for="PaymentMethodCriteria[index].Value" class="form-control my-0" style="max-width: 20ch;" />
+ </td>
+ </tr>
+ }
+ </table>
+ </div>
+ </div>
</div>
}
- <h3 class="mb-3 d-flex gap-3 align-items-center">
- <span text-translate="true">Checkout</span>
- <div id="Presets" name="Presets" class="dropdown">
- <button class="btn btn-secondary dropdown-toggle btn-sm px-3" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" text-translate="true">
- Select a preset
- </button>
- <div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
- <div id="Presets_Online" class="btcpay-list-select-item dropdown-item">
- <vc:icon symbol="pos-cart" />
- <span text-translate="true">Online</span>
- <span class="note" text-translate="true" html-translate="true">Enhance the checkout process for online purchases.<br />This assumes the payment page will be displayed on the customer's device.</span>
- </div>
- <div id="Presets_InStore" class="btcpay-list-select-item dropdown-item">
- <vc:icon symbol="nav-store" />
- <span text-translate="true">In-store</span>
- <span class="note" text-translate="true" html-translate="true">Enhance the checkout process for in-store purchases.<br />This assumes the payment page will be displayed on the merchant's device.</span>
- </div>
- </div>
- </div>
- </h3>
- <div id="CheckNFC" class="form-group d-none">
- <button type="button" class="btn btn-outline-secondary" text-translate="true">Check if NFC is supported and enabled on this device</button>
- </div>
- <div class="checkout-settings">
- <div class="form-group">
- <label asp-for="DisplayExpirationTimer" class="form-label"></label>
- <div class="input-group">
- <input inputmode="numeric" asp-for="DisplayExpirationTimer" class="form-control" style="max-width:12ch;" />
- <span class="input-group-text" text-translate="true">minutes</span>
+ <h3 id="checkout" class="settings-section__heading d-flex gap-3 align-items-center">
+ <span text-translate="true">Checkout</span>
+ <div id="Presets" name="Presets" class="dropdown">
+ <button class="btn btn-secondary dropdown-toggle btn-sm px-3" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" text-translate="true">
+ Select a preset
+ </button>
+ <div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
+ <div id="Presets_Online" class="btcpay-list-select-item dropdown-item">
+ <vc:icon symbol="pos-cart" />
+ <span text-translate="true">Online</span>
+ <span class="note" text-translate="true" html-translate="true">Enhance the checkout process for online purchases.<br />This assumes the payment page will be displayed on the customer's device.</span>
+ </div>
+ <div id="Presets_InStore" class="btcpay-list-select-item dropdown-item">
+ <vc:icon symbol="nav-store" />
+ <span text-translate="true">In-store</span>
+ <span class="note" text-translate="true" html-translate="true">Enhance the checkout process for in-store purchases.<br />This assumes the payment page will be displayed on the merchant's device.</span>
+ </div>
</div>
- <span asp-validation-for="DisplayExpirationTimer" class="text-danger"></span>
</div>
- <div class="my-3">
- <div class="d-flex">
- <input asp-for="CelebratePayment" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="CelebratePayment" class="form-check-label"></label>
- </div>
+ </h3>
+ <div class="settings-section">
+ <div id="CheckNFC" class="form-group d-none">
+ <button type="button" class="btn btn-outline-secondary" text-translate="true">Check if NFC is supported and enabled on this device</button>
</div>
- <div class="my-3">
- <div class="d-flex">
- <input asp-for="PlaySoundOnPayment" type="checkbox" class="btcpay-toggle me-3" data-bs-toggle="collapse" data-bs-target="#PlaySoundOnPaymentOptions" aria-expanded="@Model.PlaySoundOnPayment" aria-controls="PlaySoundOnPaymentOptions" />
- <label asp-for="PlaySoundOnPayment" class="form-check-label"></label>
+ <div class="checkout-settings">
+ <div class="form-group">
+ <label asp-for="DisplayExpirationTimer" class="form-label"></label>
+ <div class="input-group">
+ <input inputmode="numeric" asp-for="DisplayExpirationTimer" class="form-control" style="max-width:12ch;" />
+ <span class="input-group-text" text-translate="true">minutes</span>
+ </div>
+ <span asp-validation-for="DisplayExpirationTimer" class="text-danger"></span>
</div>
- <div class="collapse @(Model.PlaySoundOnPayment ? "show" : "")" id="PlaySoundOnPaymentOptions">
- <div class="form-group mb-0 py-3">
- <div class="d-flex align-items-center justify-content-between gap-2">
- <label asp-for="SoundFile" class="form-label"></label>
- @if (!string.IsNullOrEmpty(Model.PaymentSoundUrl))
+ <div class="my-3">
+ <div class="d-flex">
+ <input asp-for="CelebratePayment" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="CelebratePayment" class="form-check-label"></label>
+ </div>
+ </div>
+ <div class="my-3">
+ <div class="d-flex">
+ <input asp-for="PlaySoundOnPayment" type="checkbox" class="btcpay-toggle me-3" data-bs-toggle="collapse" data-bs-target="#PlaySoundOnPaymentOptions" aria-expanded="@Model.PlaySoundOnPayment" aria-controls="PlaySoundOnPaymentOptions" />
+ <label asp-for="PlaySoundOnPayment" class="form-check-label"></label>
+ </div>
+ <div class="collapse @(Model.PlaySoundOnPayment ? "show" : "")" id="PlaySoundOnPaymentOptions">
+ <div class="form-group mb-0 py-3">
+ <div class="d-flex align-items-center justify-content-between gap-2">
+ <label asp-for="SoundFile" class="form-label"></label>
+ @if (!string.IsNullOrEmpty(Model.PaymentSoundUrl))
+ {
+ <button type="submit" class="btn btn-link p-0 text-danger" name="RemoveSoundFile" value="true" permission="@Policies.CanModifyStoreSettings">
+ <vc:icon symbol="cross" />
+ <span text-translate="true">Remove</span>
+ </button>
+ }
+ </div>
+ @if (canUpload)
{
- <button type="submit" class="btn btn-link p-0 text-danger" name="RemoveSoundFile" value="true" permission="@Policies.CanModifyStoreSettings">
- <vc:icon symbol="cross" />
- <span text-translate="true">Remove</span>
- </button>
+ <div class="d-flex align-items-center gap-3">
+ <input asp-for="SoundFile" type="file" class="form-control flex-grow">
+ <audio controls src="@Model.PaymentSoundUrl" style="height:2.1rem;max-width:10.5rem;"></audio>
+ </div>
+ <span asp-validation-for="SoundFile" class="text-danger"></span>
+ }
+ else
+ {
+ <input asp-for="SoundFile" type="file" class="form-control" disabled>
+ <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
}
</div>
- @if (canUpload)
- {
- <div class="d-flex align-items-center gap-3">
- <input asp-for="SoundFile" type="file" class="form-control flex-grow">
- <audio controls src="@Model.PaymentSoundUrl" style="height:2.1rem;max-width:10.5rem;"></audio>
- </div>
- <span asp-validation-for="SoundFile" class="text-danger"></span>
- }
- else
- {
- <input asp-for="SoundFile" type="file" class="form-control" disabled>
- <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
- }
</div>
</div>
+ <div class="d-flex">
+ <input asp-for="ShowStoreHeader" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="ShowStoreHeader" class="form-check-label"></label>
+ </div>
</div>
- <div class="d-flex">
- <input asp-for="ShowStoreHeader" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="ShowStoreHeader" class="form-check-label"></label>
- </div>
- </div>
- <div class="d-flex my-3">
- <input asp-for="ShowPayInWalletButton" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="ShowPayInWalletButton" class="form-check-label"></label>
- </div>
- <div class="d-flex my-3">
- <input asp-for="OnChainWithLnInvoiceFallback" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="OnChainWithLnInvoiceFallback" class="form-check-label"></label>
- <a href="https://bitcoinqr.dev/" target="_blank" rel="noreferrer noopener">
- <vc:icon symbol="info" />
- </a>
- </div>
- <div class="d-flex my-3">
- <input asp-for="LightningAmountInSatoshi" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="LightningAmountInSatoshi" class="form-check-label"></label>
- </div>
- <div class="form-group d-flex align-items-center">
- <input asp-for="AutoDetectLanguage" type="checkbox" class="btcpay-toggle me-3" />
- <div>
- <label asp-for="AutoDetectLanguage" class="form-check-label"></label>
- <div class="form-text" text-translate="true">Detects the language of the customer's browser.</div>
+ <div class="d-flex my-3">
+ <input asp-for="ShowPayInWalletButton" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="ShowPayInWalletButton" class="form-check-label"></label>
+ </div>
+ <div class="d-flex my-3">
+ <input asp-for="OnChainWithLnInvoiceFallback" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="OnChainWithLnInvoiceFallback" class="form-check-label"></label>
+ <a href="https://bitcoinqr.dev/" target="_blank" rel="noreferrer noopener">
+ <vc:icon symbol="info" />
+ </a>
+ </div>
+ <div class="d-flex my-3">
+ <input asp-for="LightningAmountInSatoshi" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="LightningAmountInSatoshi" class="form-check-label"></label>
+ </div>
+ <div class="form-group d-flex align-items-center">
+ <input asp-for="AutoDetectLanguage" type="checkbox" class="btcpay-toggle me-3" />
+ <div>
+ <label asp-for="AutoDetectLanguage" class="form-check-label"></label>
+ <div class="form-text" text-translate="true">Detects the language of the customer's browser.</div>
+ </div>
+ </div>
+ <div class="form-group">
+ <label asp-for="DefaultLang" class="form-label"></label>
+ <select asp-for="DefaultLang" asp-items="Model.Languages" class="form-select w-auto"></select>
+ </div>
+ <div class="form-group">
+ <label asp-for="HtmlTitle" class="form-label"></label>
+ <input asp-for="HtmlTitle" class="form-control" />
+ <span asp-validation-for="HtmlTitle" class="text-danger"></span>
+ </div>
+ <div class="form-group">
+ <label asp-for="SupportUrl" class="form-label"></label>
+ <input asp-for="SupportUrl" class="form-control" />
+ <span asp-validation-for="SupportUrl" class="text-danger"></span>
+ <div class="form-text" html-translate="true">A "Contact Us" button with this link will be shown on the checkout page. Can contain the placeholders <code>{OrderId}</code> and <code>{InvoiceId}</code>. Can be any valid URI, such as a website, email, and Nostr.</div>
+ </div>
+ <div class="form-group">
+ <label asp-for="CheckoutText" class="form-label"></label>
+ <textarea asp-for="CheckoutText" class="form-control" rows="3" maxlength="500"></textarea>
+ <span asp-validation-for="CheckoutText" class="text-danger"></span>
+ <div class="form-text" text-translate="true">Custom text displayed on the checkout page below the payment details. Plain text only, newlines are supported.</div>
+ </div>
+ <div class="d-flex my-3">
+ <input asp-for="LazyPaymentMethods" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="LazyPaymentMethods" class="form-check-label"></label>
+ </div>
+ <div class="d-flex mt-3">
+ <input asp-for="RedirectAutomatically" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="RedirectAutomatically" class="form-check-label"></label>
</div>
</div>
- <div class="form-group">
- <label asp-for="DefaultLang" class="form-label"></label>
- <select asp-for="DefaultLang" asp-items="Model.Languages" class="form-select w-auto"></select>
- </div>
- <div class="form-group">
- <label asp-for="HtmlTitle" class="form-label"></label>
- <input asp-for="HtmlTitle" class="form-control" />
- <span asp-validation-for="HtmlTitle" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="SupportUrl" class="form-label"></label>
- <input asp-for="SupportUrl" class="form-control" />
- <span asp-validation-for="SupportUrl" class="text-danger"></span>
- <div class="form-text" html-translate="true">A "Contact Us" button with this link will be shown on the checkout page. Can contain the placeholders <code>{OrderId}</code> and <code>{InvoiceId}</code>. Can be any valid URI, such as a website, email, and Nostr.</div>
- </div>
- <div class="form-group">
- <label asp-for="CheckoutText" class="form-label"></label>
- <textarea asp-for="CheckoutText" class="form-control" rows="3" maxlength="500"></textarea>
- <span asp-validation-for="CheckoutText" class="text-danger"></span>
- <div class="form-text" text-translate="true">Custom text displayed on the checkout page below the payment details. Plain text only, newlines are supported.</div>
- </div>
- <div class="d-flex my-3">
- <input asp-for="LazyPaymentMethods" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="LazyPaymentMethods" class="form-check-label"></label>
- </div>
- <div class="d-flex my-2">
- <input asp-for="RedirectAutomatically" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="RedirectAutomatically" class="form-check-label"></label>
- </div>
- <h3 class="mt-5 mb-3" text-translate="true">Receipt</h3>
- <div class="d-flex my-3">
- <input asp-for="ReceiptOptions.Enabled" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="ReceiptOptions.Enabled" class="form-check-label"></label>
- </div>
- <div class="d-flex my-3">
- <input asp-for="ReceiptOptions.ShowPayments" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="ReceiptOptions.ShowPayments" class="form-check-label"></label>
- </div>
- <div class="d-flex my-3">
- <input asp-for="ReceiptOptions.ShowQR" type="checkbox" class="btcpay-toggle me-3" />
- <label asp-for="ReceiptOptions.ShowQR" class="form-check-label"></label>
+
+ <h3 id="receipt" class="settings-section__heading" text-translate="true">Receipt</h3>
+ <div class="settings-section">
+ <div class="d-flex mb-3">
+ <input asp-for="ReceiptOptions.Enabled" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="ReceiptOptions.Enabled" class="form-check-label"></label>
+ </div>
+ <div class="d-flex my-3">
+ <input asp-for="ReceiptOptions.ShowPayments" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="ReceiptOptions.ShowPayments" class="form-check-label"></label>
+ </div>
+ <div class="d-flex mt-3">
+ <input asp-for="ReceiptOptions.ShowQR" type="checkbox" class="btcpay-toggle me-3" />
+ <label asp-for="ReceiptOptions.ShowQR" class="form-check-label"></label>
+ </div>
</div>
</div>
</div>
diff --git a/BTCPayServer/Views/UIStores/GeneralSettings.cshtml b/BTCPayServer/Views/UIStores/GeneralSettings.cshtml
index 39413ad..f810294 100644
--- a/BTCPayServer/Views/UIStores/GeneralSettings.cshtml
+++ b/BTCPayServer/Views/UIStores/GeneralSettings.cshtml
@@ -9,8 +9,11 @@
Csp.UnsafeEval();
}
<form method="post" enctype="multipart/form-data" permissioned="@Policies.CanModifyStoreSettings">
- <div class="sticky-header">
- <h2 text-translate="true">Store Settings</h2>
+ <div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0" text-translate="true">Store Settings</h2>
+ <p class="text-secondary mb-0" text-translate="true">Manage your store's details, branding, and payment behavior.</p>
+ </div>
<button id="page-primary" type="submit" class="btn btn-primary" text-translate="true">Save</button>
</div>
@@ -22,237 +25,249 @@
{
<div asp-validation-summary="All" class="@(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")"></div>
}
- <div class="form-group">
- <label asp-for="Id" class="form-label"></label>
- <div class="input-group">
- <input asp-for="Id" readonly class="form-control" />
- <button type="button"
- class="btn btn-outline-secondary only-for-js"
- data-store-id-qr
- aria-label="@StringLocalizer["Show store ID QR"]">
- <vc:icon symbol="qr-code" />
- </button>
- <button type="button"
- class="btn btn-outline-secondary only-for-js clipboard-button"
- data-clipboard="@Model.Id"
- data-clipboard-confirm="@StringLocalizer["Copied"]"
- aria-label="@StringLocalizer["Copy store ID"]">
- <vc:icon symbol="actions-copy" />
- </button>
+
+ <h3 id="store-details" class="settings-section__heading" text-translate="true">Store Details</h3>
+ <div class="settings-section">
+ <div class="form-group">
+ <label asp-for="StoreName" class="form-label"></label>
+ <input asp-for="StoreName" class="form-control" />
+ <span asp-validation-for="StoreName" class="text-danger"></span>
+ </div>
+ <div class="form-group">
+ <label asp-for="Id" class="form-label"></label>
+ <div class="input-group">
+ <input asp-for="Id" readonly class="form-control" />
+ <button type="button"
+ class="btn btn-outline-secondary only-for-js"
+ data-store-id-qr
+ aria-label="@StringLocalizer["Show store ID QR"]">
+ <vc:icon symbol="qr-code" />
+ </button>
+ <button type="button"
+ class="btn btn-outline-secondary only-for-js clipboard-button"
+ data-clipboard="@Model.Id"
+ data-clipboard-confirm="@StringLocalizer["Copied"]"
+ aria-label="@StringLocalizer["Copy store ID"]">
+ <vc:icon symbol="actions-copy" />
+ </button>
+ </div>
+ </div>
+ <div class="form-group mb-0">
+ <label asp-for="StoreWebsite" class="form-label"></label>
+ <input asp-for="StoreWebsite" class="form-control" />
+ <span asp-validation-for="StoreWebsite" class="text-danger"></span>
</div>
- </div>
- <div class="form-group">
- <label asp-for="StoreName" class="form-label"></label>
- <input asp-for="StoreName" class="form-control" />
- <span asp-validation-for="StoreName" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="StoreWebsite" class="form-label"></label>
- <input asp-for="StoreWebsite" class="form-control" />
- <span asp-validation-for="StoreWebsite" class="text-danger"></span>
</div>
- <h3 class="mt-5 mb-3" text-translate="true">Branding</h3>
- <p text-translate="true">The custom color, logo and CSS are applied on the public/customer-facing pages (Invoice, Payment Request, Pull Payment, etc.). The brand color is used as the accent color for buttons, links, etc. It might get adapted to fit the light/dark color scheme.</p>
+ <h3 id="branding" class="settings-section__heading" text-translate="true">Branding</h3>
+ <div class="settings-section">
+ <p text-translate="true">The custom color, logo and CSS are applied on the public/customer-facing pages (Invoice, Payment Request, Pull Payment, etc.). The brand color is used as the accent color for buttons, links, etc. It might get adapted to fit the light/dark color scheme.</p>
- <div>
- <label asp-for="BrandColor" class="form-label"></label>
- <div class="d-flex flex-wrap">
- <div class="form-group me-4">
- <div class="input-group">
- <input id="BrandColorInput" class="form-control form-control-color flex-grow-0" type="color" style="width:3rem" aria-describedby="BrandColorValue" value="@Model.BrandColor" />
- <input asp-for="BrandColor" class="form-control form-control-color flex-grow-0 font-monospace" pattern="@ColorPalette.Pattern" style="width:5.5rem;font-size:0.9rem" />
+ <div>
+ <label asp-for="BrandColor" class="form-label"></label>
+ <div class="d-flex flex-wrap">
+ <div class="form-group me-4">
+ <div class="input-group">
+ <input id="BrandColorInput" class="form-control form-control-color flex-grow-0" type="color" style="width:3rem" aria-describedby="BrandColorValue" value="@Model.BrandColor" />
+ <input asp-for="BrandColor" class="form-control form-control-color flex-grow-0 font-monospace" pattern="@ColorPalette.Pattern" style="width:5.5rem;font-size:0.9rem" />
+ </div>
+ <span asp-validation-for="BrandColor" class="text-danger"></span>
+ </div>
+ <div class="form-group d-flex align-items-center">
+ <input asp-for="ApplyBrandColorToBackend" type="checkbox" class="btcpay-toggle me-3" disabled="@(string.IsNullOrEmpty(Model.BrandColor))"/>
+ <label asp-for="ApplyBrandColorToBackend" class="form-check-label"></label>
</div>
- <span asp-validation-for="BrandColor" class="text-danger"></span>
- </div>
- <div class="form-group d-flex align-items-center">
- <input asp-for="ApplyBrandColorToBackend" type="checkbox" class="btcpay-toggle me-3" disabled="@(string.IsNullOrEmpty(Model.BrandColor))"/>
- <label asp-for="ApplyBrandColorToBackend" class="form-check-label"></label>
</div>
</div>
- </div>
- <div class="form-group">
- <div class="d-flex align-items-center justify-content-between gap-2">
- <label asp-for="LogoFile" class="form-label"></label>
- @if (!string.IsNullOrEmpty(Model.LogoUrl))
+ <div class="form-group">
+ <div class="d-flex align-items-center justify-content-between gap-2">
+ <label asp-for="LogoFile" class="form-label"></label>
+ @if (!string.IsNullOrEmpty(Model.LogoUrl))
{
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveLogoFile" value="true">
<vc:icon symbol="cross" /><span text-translate="true">Remove</span>
</button>
}
- </div>
- @if (canUpload)
- {
- <div class="d-flex align-items-center gap-3">
- <input asp-for="LogoFile" type="file" class="form-control flex-grow">
- @if (!string.IsNullOrEmpty(Model.LogoUrl))
- {
- <img src="@Model.LogoUrl" alt="@Model.StoreName Logo" style="height:2.1rem;max-width:10.5rem;"/>
- }
</div>
- <span asp-validation-for="LogoFile" class="text-danger"></span>
- }
- else
- {
- <input asp-for="LogoFile" type="file" class="form-control" disabled>
- <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
- }
- </div>
- <div class="form-group">
- <div class="d-flex align-items-center justify-content-between gap-2">
- <label asp-for="CssFile" class="form-label"></label>
- @if (!string.IsNullOrEmpty(Model.CssUrl))
+ @if (canUpload)
+ {
+ <div class="d-flex align-items-center gap-3">
+ <input asp-for="LogoFile" type="file" class="form-control flex-grow">
+ @if (!string.IsNullOrEmpty(Model.LogoUrl))
+ {
+ <img src="@Model.LogoUrl" alt="@Model.StoreName Logo" style="height:2.1rem;max-width:10.5rem;"/>
+ }
+ </div>
+ <span asp-validation-for="LogoFile" class="text-danger"></span>
+ }
+ else
+ {
+ <input asp-for="LogoFile" type="file" class="form-control" disabled>
+ <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
+ }
+ </div>
+ <div class="form-group mb-0">
+ <div class="d-flex align-items-center justify-content-between gap-2">
+ <label asp-for="CssFile" class="form-label"></label>
+ @if (!string.IsNullOrEmpty(Model.CssUrl))
{
<button type="submit" class="btn btn-link p-0 text-danger" name="RemoveCssFile" value="true">
<vc:icon symbol="cross" /><span text-translate="true">Remove</span>
</button>
}
- </div>
- @if (canUpload)
- {
- <div class="d-flex align-items-center gap-3">
- <input asp-for="CssFile" type="file" class="form-control flex-grow">
- @if (!string.IsNullOrEmpty(Model.CssUrl))
- {
- <a href="@Model.CssUrl" target="_blank" rel="noreferrer noopener" class="text-nowrap" text-translate="true">Custom CSS</a>
- }
</div>
- <span asp-validation-for="LogoFile" class="text-danger"></span>
- }
- else
- {
- <input asp-for="CssFile" type="file" class="form-control" disabled>
- <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
- }
+ @if (canUpload)
+ {
+ <div class="d-flex align-items-center gap-3">
+ <input asp-for="CssFile" type="file" class="form-control flex-grow">
+ @if (!string.IsNullOrEmpty(Model.CssUrl))
+ {
+ <a href="@Model.CssUrl" target="_blank" rel="noreferrer noopener" class="text-nowrap" text-translate="true">Custom CSS</a>
+ }
+ </div>
+ <span asp-validation-for="CssFile" class="text-danger"></span>
+ }
+ else
+ {
+ <input asp-for="CssFile" type="file" class="form-control" disabled>
+ <div class="form-text">@ViewLocalizer["In order to upload, a {0} must be configured.", Html.ActionLink(StringLocalizer["file storage"], "Files", "UIServer")]</div>
+ }
+ </div>
</div>
- <h3 class="mt-5 mb-3" text-translate="true">Payment</h3>
- <div class="form-group">
- <label asp-for="DefaultCurrency" class="form-label"></label>
- <input asp-for="DefaultCurrency" class="form-control w-auto" currency-selection />
- <span asp-validation-for="DefaultCurrency" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="AdditionalTrackedRates" class="form-label"></label>
- <input asp-for="AdditionalTrackedRates" class="form-control" placeholder="@StringLocalizer["Comma-separated list of currencies (eg. USD,EUR,JPY)"]" />
- <div class="form-text" text-translate="true">The rates of those currencies, in addition to the default currency, will be recorded when a new invoice is created. The rates will then be accessible through reports.</div>
- <span asp-validation-for="AdditionalTrackedRates" class="text-danger"></span>
- </div>
- <div class="form-group mt-4">
- <label asp-for="NetworkFeeMode" class="form-label"></label>
- <a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- @* For whatever reason wrapping the select with this div fixes this Safari bug: https://github.com/btcpayserver/btcpayserver/issues/3699 *@
- <div class="overflow-hidden">
- <select asp-for="NetworkFeeMode" class="form-select w-auto">
- <option value="MultiplePaymentsOnly" text-translate="true">... only if the customer makes more than one payment for the invoice</option>
- <option value="Always" text-translate="true">... on every payment</option>
- <option value="Never" text-translate="true">Never add network fee</option>
- </select>
+ <h3 id="payment" class="settings-section__heading" text-translate="true">Payment</h3>
+ <div class="settings-section">
+ <div class="form-group">
+ <label asp-for="DefaultCurrency" class="form-label"></label>
+ <input asp-for="DefaultCurrency" class="form-control w-auto" currency-selection />
+ <span asp-validation-for="DefaultCurrency" class="text-danger"></span>
</div>
- </div>
- <div class="form-group">
- <label asp-for="InvoiceExpiration" class="form-label"></label>
- <a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- <div class="input-group">
- <input inputmode="numeric" asp-for="InvoiceExpiration" class="form-control" style="max-width:12ch;"/>
- <span class="input-group-text" text-translate="true">minutes</span>
+ <div class="form-group">
+ <label asp-for="AdditionalTrackedRates" class="form-label"></label>
+ <input asp-for="AdditionalTrackedRates" class="form-control" placeholder="@StringLocalizer["Comma-separated list of currencies (eg. USD,EUR,JPY)"]" />
+ <div class="form-text" text-translate="true">The rates of those currencies, in addition to the default currency, will be recorded when a new invoice is created. The rates will then be accessible through reports.</div>
+ <span asp-validation-for="AdditionalTrackedRates" class="text-danger"></span>
</div>
- <span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="PaymentTolerance" class="form-label"></label>
- <a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- <div class="input-group">
- <input inputmode="decimal" asp-for="PaymentTolerance" class="form-control" style="max-width:12ch;"/>
- <span class="input-group-text" text-translate="true">percent</span>
+ <div class="form-group mt-4">
+ <label asp-for="NetworkFeeMode" class="form-label"></label>
+ <a href="https://docs.btcpayserver.org/FAQ/Stores/#add-network-fee-to-invoice-vary-with-mining-fees" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ @* For whatever reason wrapping the select with this div fixes this Safari bug: https://github.com/btcpayserver/btcpayserver/issues/3699 *@
+ <div class="overflow-hidden">
+ <select asp-for="NetworkFeeMode" class="form-select w-auto">
+ <option value="MultiplePaymentsOnly" text-translate="true">... only if the customer makes more than one payment for the invoice</option>
+ <option value="Always" text-translate="true">... on every payment</option>
+ <option value="Never" text-translate="true">Never add network fee</option>
+ </select>
+ </div>
</div>
- <span asp-validation-for="PaymentTolerance" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="BOLT11Expiration" class="form-label"></label>
- <div class="input-group">
- <input inputmode="numeric" asp-for="BOLT11Expiration" class="form-control" style="max-width:12ch;"/>
- <span class="input-group-text" text-translate="true">days</span>
+ <div class="form-group">
+ <label asp-for="InvoiceExpiration" class="form-label"></label>
+ <a href="https://docs.btcpayserver.org/FAQ/Stores/#invoice-expires-if-the-full-amount-has-not-been-paid-after-minutes" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ <div class="input-group">
+ <input inputmode="numeric" asp-for="InvoiceExpiration" class="form-control" style="max-width:12ch;"/>
+ <span class="input-group-text" text-translate="true">minutes</span>
+ </div>
+ <span asp-validation-for="InvoiceExpiration" class="text-danger"></span>
</div>
- <span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="MonitoringExpiration" class="form-label"></label>
- <a href="https://docs.btcpayserver.org/FAQ/Stores/#payment-invalid-if-transactions-fails-to-confirm-minutes-after-invoice-expiration" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- <div class="input-group">
- <input inputmode="numeric" asp-for="MonitoringExpiration" class="form-control" style="max-width:10ch;"/>
- <span class="input-group-text" text-translate="true">minutes</span>
+ <div class="form-group">
+ <label asp-for="PaymentTolerance" class="form-label"></label>
+ <a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-paid-even-if-the-paid-amount-is-less-than-expected" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ <div class="input-group">
+ <input inputmode="decimal" asp-for="PaymentTolerance" class="form-control" style="max-width:12ch;"/>
+ <span class="input-group-text" text-translate="true">percent</span>
+ </div>
+ <span asp-validation-for="PaymentTolerance" class="text-danger"></span>
</div>
- <span asp-validation-for="MonitoringExpiration" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="SpeedPolicy" class="form-label"></label>
- <a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- <select asp-for="SpeedPolicy" class="form-select w-auto" onchange="document.getElementById('unconfirmed-warning').hidden = this.value !== '0'">
- <option value="0" text-translate="true">Is unconfirmed</option>
- <option value="1" text-translate="true">Has at least 1 confirmation</option>
- <option value="3" text-translate="true">Has at least 2 confirmations</option>
- <option value="2" text-translate="true">Has at least 6 confirmations</option>
- </select>
- <p class="info-note my-3 text-warning" id="unconfirmed-warning" role="alert" hidden="@(Model.SpeedPolicy != 0)">
- <vc:icon symbol="warning"/>
- <span text-translate="true">Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.</span>
- </p>
- <span asp-validation-for="SpeedPolicy" class="text-danger"></span>
- </div>
- <div class="form-group d-flex align-items-center">
- <input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-3"/>
- <label asp-for="AnyoneCanCreateInvoice" class="form-check-label me-1"></label>
- <a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
- <vc:icon symbol="info"/>
- </a>
- </div>
- <div class="d-flex align-items-center my-3">
- <input asp-for="ShowRecommendedFee" type="checkbox" class="btcpay-toggle me-3" />
- <div>
- <label asp-for="ShowRecommendedFee" class="form-check-label"></label>
- <div class="form-text" text-translate="true">Fee will be shown for BTC and LTC onchain payments only.</div>
+ <div class="form-group">
+ <label asp-for="BOLT11Expiration" class="form-label"></label>
+ <div class="input-group">
+ <input inputmode="numeric" asp-for="BOLT11Expiration" class="form-control" style="max-width:12ch;"/>
+ <span class="input-group-text" text-translate="true">days</span>
+ </div>
+ <span asp-validation-for="BOLT11Expiration" class="text-danger"></span>
</div>
- </div>
- <div class="form-group">
- <label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
- <div class="input-group">
- <input inputmode="numeric" asp-for="RecommendedFeeBlockTarget" class="form-control" min="1" style="max-width:10ch" />
- <span class="input-group-text" text-translate="true">blocks</span>
+ <div class="form-group">
+ <label asp-for="MonitoringExpiration" class="form-label"></label>
+ <a href="https://docs.btcpayserver.org/FAQ/Stores/#payment-invalid-if-transactions-fails-to-confirm-minutes-after-invoice-expiration" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ <div class="input-group">
+ <input inputmode="numeric" asp-for="MonitoringExpiration" class="form-control" style="max-width:10ch;"/>
+ <span class="input-group-text" text-translate="true">minutes</span>
+ </div>
+ <span asp-validation-for="MonitoringExpiration" class="text-danger"></span>
+ </div>
+ <div class="form-group">
+ <label asp-for="SpeedPolicy" class="form-label"></label>
+ <a href="https://docs.btcpayserver.org/FAQ/Stores/#consider-the-invoice-confirmed-when-the-payment-transaction" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ <select asp-for="SpeedPolicy" class="form-select w-auto" onchange="document.getElementById('unconfirmed-warning').hidden = this.value !== '0'">
+ <option value="0" text-translate="true">Is unconfirmed</option>
+ <option value="1" text-translate="true">Has at least 1 confirmation</option>
+ <option value="3" text-translate="true">Has at least 2 confirmations</option>
+ <option value="2" text-translate="true">Has at least 6 confirmations</option>
+ </select>
+ <p class="info-note my-3 text-warning" id="unconfirmed-warning" role="alert" hidden="@(Model.SpeedPolicy != 0)">
+ <vc:icon symbol="warning"/>
+ <span text-translate="true">Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.</span>
+ </p>
+ <span asp-validation-for="SpeedPolicy" class="text-danger"></span>
+ </div>
+ <div class="form-group d-flex align-items-center">
+ <input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="btcpay-toggle me-3"/>
+ <label asp-for="AnyoneCanCreateInvoice" class="form-check-label me-1"></label>
+ <a href="https://docs.btcpayserver.org/FAQ/Stores/#allow-anyone-to-create-invoice" target="_blank" rel="noreferrer noopener" title="@StringLocalizer["More information..."]">
+ <vc:icon symbol="info"/>
+ </a>
+ </div>
+ <div class="d-flex align-items-center my-3">
+ <input asp-for="ShowRecommendedFee" type="checkbox" class="btcpay-toggle me-3" />
+ <div>
+ <label asp-for="ShowRecommendedFee" class="form-check-label"></label>
+ <div class="form-text" text-translate="true">Fee will be shown for BTC and LTC onchain payments only.</div>
+ </div>
+ </div>
+ <div class="form-group mb-0">
+ <label asp-for="RecommendedFeeBlockTarget" class="form-label"></label>
+ <div class="input-group">
+ <input inputmode="numeric" asp-for="RecommendedFeeBlockTarget" class="form-control" min="1" style="max-width:10ch" />
+ <span class="input-group-text" text-translate="true">blocks</span>
+ </div>
+ <span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
</div>
- <span asp-validation-for="RecommendedFeeBlockTarget" class="text-danger"></span>
</div>
</div>
</div>
</form>
-<div permission="@Policies.CanModifyStoreSettings">
- <h3 class="mt-5 mb-3" text-translate="true">Additional Actions</h3>
- <div id="danger-zone" class="d-flex flex-wrap align-items-center gap-3 mb-5 mt-2">
- <form asp-action="ToggleArchive" asp-route-storeId="@Model.Id" method="post">
- <button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
- @if (Model.Archived)
- {
- <span class="text-nowrap" data-bs-toggle="tooltip" title="@StringLocalizer["Unarchive this store"]" text-translate="true">Unarchive this store</span>
- }
- else
- {
- <span class="text-nowrap" data-bs-toggle="tooltip" title="@StringLocalizer["Archive this store so that it does not appear in the stores list by default"]" text-translate="true">Archive this store</span>
- }
- </button>
- </form>
- <a id="DeleteStore" class="btn btn-outline-danger" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="@ViewLocalizer["The store <strong>{0}</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store.", Html.Encode(Model.StoreName)]" data-confirm-input="@StringLocalizer["DELETE"]" text-translate="true">Delete this store</a>
+<div class="row" permission="@Policies.CanModifyStoreSettings">
+ <div class="col-xxl-constrain col-xl-8">
+ <h3 id="additional-actions" class="settings-section__heading settings-section__heading--spaced" text-translate="true">Additional Actions</h3>
+ <div class="settings-section">
+ <div id="danger-zone" class="d-flex flex-wrap align-items-center gap-3">
+ <form asp-action="ToggleArchive" asp-route-storeId="@Model.Id" method="post">
+ <button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
+ @if (Model.Archived)
+ {
+ <span class="text-nowrap" data-bs-toggle="tooltip" title="@StringLocalizer["Unarchive this store"]" text-translate="true">Unarchive this store</span>
+ }
+ else
+ {
+ <span class="text-nowrap" data-bs-toggle="tooltip" title="@StringLocalizer["Archive this store so that it does not appear in the stores list by default"]" text-translate="true">Archive this store</span>
+ }
+ </button>
+ </form>
+ <a id="DeleteStore" class="btn btn-outline-danger" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="@ViewLocalizer["The store <strong>{0}</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store.", Html.Encode(Model.StoreName)]" data-confirm-input="@StringLocalizer["DELETE"]" text-translate="true">Delete this store</a>
+ </div>
+ </div>
</div>
</div>
diff --git a/BTCPayServer/Views/UIStores/Rates.cshtml b/BTCPayServer/Views/UIStores/Rates.cshtml
index bb1072b..b7037b5 100644
--- a/BTCPayServer/Views/UIStores/Rates.cshtml
+++ b/BTCPayServer/Views/UIStores/Rates.cshtml
@@ -5,8 +5,11 @@
}
<form method="post" permissioned="@Policies.CanModifyStoreSettings">
- <div class="sticky-header">
- <vc:title-header />
+ <div class="sticky-header align-items-start">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData["Title"]</h2>
+ <p class="text-secondary mb-0" text-translate="true">Choose where exchange rates come from and how they're applied when pricing invoices.</p>
+ </div>
<button id="page-primary" name="command" type="submit" class="btn btn-primary" value="Save" text-translate="true">Save</button>
</div>
<partial name="_StatusMessage" />
@@ -17,9 +20,8 @@
<div class="row">
<div class="col-xxl-constrain col-xl-8">
- <fieldset>
- <legend text-translate="true" class="mb-3">Rate Source</legend>
-
+ <h3 id="rate-source" class="settings-section__heading" text-translate="true">Rate Source</h3>
+ <div class="settings-section" role="group" aria-labelledby="rate-source">
@if (Model.PrimarySource.ShowScripting || Model.FallbackSource?.ShowScripting is true)
{
<div class="form-group">
@@ -86,11 +88,11 @@ X_X = kraken(X_X);</code></pre>
}
<fieldset>
- <legend text-translate="true">Primary rate source</legend>
+ <legend class="settings-section__subheading" text-translate="true">Primary rate source</legend>
@Html.EditorFor(m => m.PrimarySource, "/Views/UIStores/Rates.Source.cshtml")
</fieldset>
- <fieldset>
- <legend text-translate="true">Fallback rate source</legend>
+ <fieldset class="mb-0">
+ <legend class="settings-section__subheading" text-translate="true">Fallback rate source</legend>
<div class="form-group d-flex align-items-center pt-2">
<input asp-for="HasFallback" type="checkbox" class="btcpay-toggle me-3" data-bs-toggle="collapse" data-bs-target="#FallbackSection"
aria-expanded="@Model.HasFallback" aria-controls="FallbackSection" />
@@ -103,14 +105,11 @@ X_X = kraken(X_X);</code></pre>
@Html.EditorFor(m => m.FallbackSource, "/Views/UIStores/Rates.Source.cshtml")
</div>
</fieldset>
- </fieldset>
- </div>
- </div>
- <div class="row">
- <div class="col-xxl-constrain col-xl-8">
- <fieldset>
- <legend text-translate="true" class="mb-3">Rate Spread</legend>
- <div class="form-group">
+ </div>
+
+ <h3 id="rate-spread" class="settings-section__heading" text-translate="true">Rate Spread</h3>
+ <div class="settings-section">
+ <div class="form-group mb-0">
<label asp-for="Spread" class="form-label"></label>
<div class="input-group">
<input inputmode="decimal" asp-for="Spread" class="form-control" />
@@ -118,14 +117,11 @@ X_X = kraken(X_X);</code></pre>
</div>
<span asp-validation-for="Spread" class="text-danger"></span>
</div>
- </fieldset>
- </div>
- </div>
- <div class="row">
- <div class="col-xxl-constrain col-xl-8">
- <fieldset class="mb-3">
- <legend text-translate="true">Currency Pair Testing</legend>
- <div class="form-group">
+ </div>
+
+ <h3 id="currency-pair-testing" class="settings-section__heading" text-translate="true">Currency Pair Testing</h3>
+ <div class="settings-section">
+ <div class="form-group mb-0">
<label asp-for="ScriptTest" class="form-label"><span text-translate="true">Currency pairs to test against your rule</span> (<code>DOGE_USD,DOGE_CAD,BTC_CAD,BTC_USD</code>)</label>
<div class="d-flex">
<input asp-for="ScriptTest" class="form-control" placeholder="BTC_USD, BTC_CAD" />
@@ -138,7 +134,7 @@ X_X = kraken(X_X);</code></pre>
@if (Model.TestRateRules != null)
{
- <h5 text-translate="true" id="TestResult">Test Results:</h5>
+ <h5 text-translate="true" id="TestResult" class="mt-4">Test Results:</h5>
<div class="table-responsive-md">
<table class="table table-hover">
<tbody>
@@ -166,19 +162,16 @@ X_X = kraken(X_X);</code></pre>
</table>
</div>
}
- </fieldset>
- </div>
- </div>
- <div class="row">
- <div class="col-xxl-constrain col-xl-8">
- <div class="form-group">
- <fieldset class="mb-3">
- <legend text-translate="true">Default Currency Pairs</legend>
+ </div>
+
+ <h3 id="default-currency-pairs" class="settings-section__heading" text-translate="true">Default Currency Pairs</h3>
+ <div class="settings-section">
+ <div class="form-group mb-0">
<label asp-for="DefaultCurrencyPairs"
class="form-label">@ViewLocalizer["Query pairs via REST by querying {0} without the need to specify currencyPairs.", Html.ActionLink(StringLocalizer["this link"], "GetRates2", "BitpayRate", new { storeId = Model.StoreId }, new { target = "_blank" })]</label>
<input asp-for="DefaultCurrencyPairs" class="form-control" placeholder="BTC_USD, BTC_CAD" />
<span asp-validation-for="DefaultCurrencyPairs" class="text-danger"></span>
- </fieldset>
+ </div>
</div>
</div>
</div>
diff --git a/BTCPayServer/Views/UIStores/StoreUsers.cshtml b/BTCPayServer/Views/UIStores/StoreUsers.cshtml
index 801d21a..c60783d 100644
--- a/BTCPayServer/Views/UIStores/StoreUsers.cshtml
+++ b/BTCPayServer/Views/UIStores/StoreUsers.cshtml
@@ -17,25 +17,29 @@
}
</style>
}
-<h2 class="mb-2 mb-lg-3">@ViewData["Title"]</h2>
+<div class="sticky-header">
+ <div class="d-flex flex-column gap-1 flex-grow-1">
+ <h2 class="mb-0">@ViewData["Title"]</h2>
+ <p class="text-secondary mb-0">@ViewLocalizer["Give other registered BTCPay Server users access to your store. See the {0} for granted permissions.", Html.ActionLink(StringLocalizer["roles"], "ListRoles", "UIStores", new { storeId })]</p>
+ </div>
+</div>
<partial name="_StatusMessage" />
<div class="row">
<div class="col-xxl-constrain col-xl-8">
- <p>@ViewLocalizer["Give other registered BTCPay Server users access to your store. See the {0} for granted permissions.", Html.ActionLink(StringLocalizer["roles"], "ListRoles", "UIStores", new { storeId })]</p>
-
@if (!ViewContext.ModelState.IsValid)
{
<div asp-validation-summary="All" class="@(ViewContext.ModelState.ErrorCount.Equals(1) ? "no-marker" : "")"></div>
}
- <form method="post" class="d-flex flex-wrap align-items-center gap-3" permission="@Policies.CanModifyStoreSettings">
+ <div class="settings-section">
+ <form method="post" class="d-flex flex-wrap align-items-center gap-3 mb-4" permission="@Policies.CanModifyStoreSettings">
<input asp-for="Email" type="text" class="form-control" placeholder="@StringLocalizer["user@example.com"]" style="flex: 1 1 14rem">
<select asp-for="Role" class="form-select" asp-items="roles"></select>
<button type="submit" role="button" class="btn btn-primary text-nowrap flex-grow-1 flex-sm-grow-0" id="AddUser" text-translate="true">Add User</button>
</form>
- <div class="table-responsive-md">
- <table class="table table-hover">
+ <div class="table-responsive-md settings-section__table">
+ <table class="table table-hover mb-0">
<thead>
<tr>
<th text-translate="true">Email</th>
@@ -62,6 +66,7 @@
</tbody>
</table>
</div>
+ </div>
</div>
</div>
diff --git a/BTCPayServer/wwwroot/main/rtl/site.rtl.css b/BTCPayServer/wwwroot/main/rtl/site.rtl.css
index 1de1e8f..a51acad 100644
--- a/BTCPayServer/wwwroot/main/rtl/site.rtl.css
+++ b/BTCPayServer/wwwroot/main/rtl/site.rtl.css
@@ -195,6 +195,10 @@ h2 .icon.icon-info {
#mainContent section p {
max-width: 46em;
}
+/* Header subtitle spans its column; a button (when present) constrains it, not a fixed cap */
+#mainContent .sticky-header p {
+ max-width: none;
+}
/* Invoices */
.invoice-details-row {
@@ -309,12 +313,42 @@ h2 .icon.icon-info {
page-break-inside: avoid;
}
.actions-col,
- .sticky-header > div:last-child,
+ .sticky-header > div:last-child:not(:first-child),
.sticky-header > .btn:last-child {
display: none;
}
}
+/* Settings section containers (see issue #6901) */
+.settings-section__heading {
+ margin-top: var(--btcpay-space-l);
+ margin-bottom: var(--btcpay-space-m);
+ scroll-margin-top: calc(var(--sticky-header-height) + var(--btcpay-space-s));
+}
+/* The first heading sits tighter under the page's sticky header */
+.settings-section__heading:first-of-type {
+ margin-top: var(--btcpay-space-s);
+}
+/* …unless it follows a headingless section, then keep the full top margin */
+.settings-section__heading.settings-section__heading--spaced {
+ margin-top: var(--btcpay-space-l);
+}
+.settings-section {
+ border-radius: var(--btcpay-border-radius-l);
+ background: var(--btcpay-bg-tile);
+ padding: var(--btcpay-space-l);
+}
+/* Sub-heading inside a section: one ladder step below the section heading */
+.settings-section__subheading {
+ font-size: var(--btcpay-font-size-l);
+}
+/* A table trims the section's extra vertical padding to half.
+ Scoped under .settings-section to out-specify `main [class*='table-responsive']`. */
+.settings-section .settings-section__table {
+ margin-top: calc(-0.5 * var(--btcpay-space-l));
+ margin-bottom: calc(-0.5 * var(--btcpay-space-l));
+}
+
/* Richtext editor */
.note-editor .table.table-sm {
border-collapse: collapse !important;
diff --git a/BTCPayServer/wwwroot/main/site.css b/BTCPayServer/wwwroot/main/site.css
index 913c3f7..496503e 100644
--- a/BTCPayServer/wwwroot/main/site.css
+++ b/BTCPayServer/wwwroot/main/site.css
@@ -195,6 +195,10 @@ h2 .icon.icon-info {
#mainContent section p {
max-width: 46em;
}
+/* Header subtitle spans its column; a button (when present) constrains it, not a fixed cap */
+#mainContent .sticky-header p {
+ max-width: none;
+}
/* Invoices */
.invoice-details-row {
@@ -309,12 +313,42 @@ h2 .icon.icon-info {
page-break-inside: avoid;
}
.actions-col,
- .sticky-header > div:last-child,
+ .sticky-header > div:last-child:not(:first-child),
.sticky-header > .btn:last-child {
display: none;
}
}
+/* Settings section containers (see issue #6901) */
+.settings-section__heading {
+ margin-top: var(--btcpay-space-l);
+ margin-bottom: var(--btcpay-space-m);
+ scroll-margin-top: calc(var(--sticky-header-height) + var(--btcpay-space-s));
+}
+/* The first heading sits tighter under the page's sticky header */
+.settings-section__heading:first-of-type {
+ margin-top: var(--btcpay-space-s);
+}
+/* …unless it follows a headingless section, then keep the full top margin */
+.settings-section__heading.settings-section__heading--spaced {
+ margin-top: var(--btcpay-space-l);
+}
+.settings-section {
+ border-radius: var(--btcpay-border-radius-l);
+ background: var(--btcpay-bg-tile);
+ padding: var(--btcpay-space-l);
+}
+/* Sub-heading inside a section: one ladder step below the section heading */
+.settings-section__subheading {
+ font-size: var(--btcpay-font-size-l);
+}
+/* A table trims the section's extra vertical padding to half.
+ Scoped under .settings-section to out-specify `main [class*='table-responsive']`. */
+.settings-section .settings-section__table {
+ margin-top: calc(-0.5 * var(--btcpay-space-l));
+ margin-bottom: calc(-0.5 * var(--btcpay-space-l));
+}
+
/* Richtext editor */
.note-editor .table.table-sm {
border-collapse: collapse !important;
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.