Empty states: Setup guide for store and wallet (#3255)

* Store Selector: Create first store button

* Add setup guide to homepage

* Policy update for stores list

* Fix test

* Initial create store button as primary

* Add notifications list to homepage

* Remove back to list from store create view

* Adapt content padding on desktop

* Add store home view with setup guide

* Fix active page nav highlighting

* Test fix

* Remove What's Next section

* Rename Store Home to Dashboard

* Fix Lightning setup link

* Add tests for store setup guide

* Update BTCPayServer/Views/Home/Home.cshtml

Co-authored-by: Pavlenex <pavle@pavle.org>

* Update BTCPayServer/Views/Stores/Dashboard.cshtml

Co-authored-by: Pavlenex <pavle@pavle.org>

* Remove setup guide on global homepage

* Remove Shopify setup link from nav

* Fix content container max-width on desktop

Co-authored-by: Pavlenex <pavle@pavle.org>
This commit is contained in:
d11n
2022-01-13 09:08:15 +01:00
committed by GitHub
parent 0c0235a56e
commit 69d1acc797
25 changed files with 522 additions and 284 deletions

View File

@@ -14,7 +14,7 @@ namespace BTCPayServer.Abstractions.Extensions
public static void SetActivePage<T>(this ViewDataDictionary viewData, T activePage, string title = null, string activeId = null) public static void SetActivePage<T>(this ViewDataDictionary viewData, T activePage, string title = null, string activeId = null)
where T : IConvertible where T : IConvertible
{ {
SetActivePage(viewData, activePage.ToString(), activePage.GetType().Name, title, activeId); SetActivePage(viewData, activePage.ToString(), activePage.GetType().ToString(), title, activeId);
} }
public static void SetActivePage(this ViewDataDictionary viewData, string activePage, string category, string title = null, string activeId = null) public static void SetActivePage(this ViewDataDictionary viewData, string activePage, string category, string title = null, string activeId = null)
@@ -29,7 +29,7 @@ namespace BTCPayServer.Abstractions.Extensions
public static void SetActiveCategory<T>(this ViewDataDictionary viewData, T activeCategory) public static void SetActiveCategory<T>(this ViewDataDictionary viewData, T activeCategory)
{ {
viewData[ACTIVE_CATEGORY_KEY] = activeCategory; SetActiveCategory(viewData, activeCategory.ToString());
} }
public static void SetActiveCategory(this ViewDataDictionary viewData, string activeCategory) public static void SetActiveCategory(this ViewDataDictionary viewData, string activeCategory)
@@ -41,6 +41,7 @@ namespace BTCPayServer.Abstractions.Extensions
{ {
return IsActiveCategory(viewData, category.ToString(), id); return IsActiveCategory(viewData, category.ToString(), id);
} }
public static string IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null) public static string IsActiveCategory(this ViewDataDictionary viewData, string category, object id = null)
{ {
if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY)) if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY))
@@ -57,8 +58,9 @@ namespace BTCPayServer.Abstractions.Extensions
public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null) public static string IsActivePage<T>(this ViewDataDictionary viewData, T page, object id = null)
where T : IConvertible where T : IConvertible
{ {
return IsActivePage(viewData, page.ToString(), page.GetType().Name, id); return IsActivePage(viewData, page.ToString(), page.GetType().ToString(), id);
} }
public static string IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null) public static string IsActivePage(this ViewDataDictionary viewData, string page, string category, object id = null)
{ {
if (!viewData.ContainsKey(ACTIVE_PAGE_KEY)) if (!viewData.ContainsKey(ACTIVE_PAGE_KEY))

View File

@@ -142,11 +142,16 @@ namespace BTCPayServer.Tests
public (string storeName, string storeId) CreateNewStore(bool keepId = true) public (string storeName, string storeId) CreateNewStore(bool keepId = true)
{ {
Driver.WaitForElement(By.Id("StoreSelectorToggle")).Click(); // If there's no store yet, there is no dropdown toggle
Driver.WaitForElement(By.Id("StoreSelectorMenuItem-Create")).Click(); if (Driver.PageSource.Contains("id=\"StoreSelectorToggle\""))
{
Driver.FindElement(By.Id("StoreSelectorToggle")).Click();
}
Driver.WaitForElement(By.Id("StoreSelectorCreate")).Click();
var name = "Store" + RandomUtils.GetUInt64(); var name = "Store" + RandomUtils.GetUInt64();
Driver.WaitForElement(By.Id("Name")).SendKeys(name); Driver.WaitForElement(By.Id("Name")).SendKeys(name);
Driver.WaitForElement(By.Id("Create")).Click(); Driver.WaitForElement(By.Id("Create")).Click();
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.GeneralSettings.ToString()}")).Click(); Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.GeneralSettings.ToString()}")).Click();
var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value"); var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.PaymentMethods.ToString()}")).Click(); Driver.FindElement(By.Id($"SectionNav-{StoreNavPages.PaymentMethods.ToString()}")).Click();
@@ -221,8 +226,12 @@ namespace BTCPayServer.Tests
/// <param name="cryptoCode"></param> /// <param name="cryptoCode"></param>
/// <param name="derivationScheme"></param> /// <param name="derivationScheme"></param>
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]") public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
{
if (Driver.PageSource.Contains($"id=\"Modify{cryptoCode}\""))
{ {
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click(); Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
}
Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click(); Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
Driver.FindElement(By.Id("ImportXpubLink")).Click(); Driver.FindElement(By.Id("ImportXpubLink")).Click();
Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme); Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme);
@@ -242,11 +251,14 @@ namespace BTCPayServer.Tests
public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true) public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true)
{ {
cryptoCode ??= "BTC"; cryptoCode ??= "BTC";
if (Driver.PageSource.Contains($"id=\"Modify-Lightning{cryptoCode}\""))
{
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click(); Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
}
if (Driver.PageSource.Contains("id=\"SetupLightningNodeLink\"")) if (Driver.PageSource.Contains("id=\"SetupLightningNodeLink\""))
{ {
Driver.FindElement(By.Id($"SetupLightningNodeLink")).Click(); Driver.FindElement(By.Id("SetupLightningNodeLink")).Click();
} }
var connectionString = connectionType switch var connectionString = connectionType switch
@@ -352,13 +364,14 @@ namespace BTCPayServer.Tests
{ {
GoToStore(null, storeNavPage); GoToStore(null, storeNavPage);
} }
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.PaymentMethods) public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.PaymentMethods)
{ {
if (storeId is null) if (storeId is not null)
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
else
GoToUrl($"/stores/{storeId}/"); GoToUrl($"/stores/{storeId}/");
Driver.FindElement(By.Id("StoreNav-StoreSettings")).Click();
if (storeNavPage != StoreNavPages.PaymentMethods) if (storeNavPage != StoreNavPages.PaymentMethods)
{ {
// FIXME: Review and optimize this once we decided on where which items belong // FIXME: Review and optimize this once we decided on where which items belong

View File

@@ -180,7 +180,6 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Email")).SendKeys(email); s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("abc???"); s.Driver.FindElement(By.Id("Password")).SendKeys("abc???");
s.Driver.FindElement(By.Id("LoginButton")).Click(); s.Driver.FindElement(By.Id("LoginButton")).Click();
Assert.True(s.Driver.PageSource.Contains("Stores"), "Can't Access Stores");
s.GoToProfile(); s.GoToProfile();
s.ClickOnAllSectionLinks(); s.ClickOnAllSectionLinks();
@@ -382,6 +381,30 @@ namespace BTCPayServer.Tests
TestUtils.Eventually(() => Assert.Contains("Settled (marked)", s.Driver.PageSource)); TestUtils.Eventually(() => Assert.Contains("Settled (marked)", s.Driver.PageSource));
} }
[Fact(Timeout = TestTimeout)]
public async Task CanSetupStoreViaGuide()
{
using (var s = CreateSeleniumTester())
{
await s.StartAsync();
s.RegisterNewUser();
s.GoToUrl("/");
Assert.False(s.Driver.PageSource.Contains("id=\"StoreSelectorDropdown\""), "Store selector dropdown should not be present");
Assert.True(s.Driver.PageSource.Contains("id=\"StoreSelectorCreate\""), "Store selector create button should be present");
// verify steps for store creation are displayed correctly
s.Driver.FindElement(By.Id("SetupGuide-Store")).Click();
Assert.Contains("/stores/create", s.Driver.Url);
s.CreateNewStore();
s.GoToUrl("/");
Assert.True(s.Driver.PageSource.Contains("id=\"StoreSelectorDropdown\""), "Store selector dropdown should be present");
Assert.False(s.Driver.PageSource.Contains("id=\"SetupGuide\""), "Setup guide should not be present");
}
}
[Fact(Timeout = TestTimeout)] [Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")] [Trait("Lightning", "Lightning")]
public async Task CanCreateStores() public async Task CanCreateStores()
@@ -391,7 +414,8 @@ namespace BTCPayServer.Tests
s.Server.ActivateLightning(); s.Server.ActivateLightning();
await s.StartAsync(); await s.StartAsync();
var alice = s.RegisterNewUser(true); var alice = s.RegisterNewUser(true);
(string storeName, _) = s.CreateNewStore(); (string storeName, string storeId) = s.CreateNewStore();
var storeUrl = $"/stores/{storeId}";
var onchainHint = "Set up your wallet to receive payments at your store."; var onchainHint = "Set up your wallet to receive payments at your store.";
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments."; var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
@@ -405,15 +429,25 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.PageSource.Contains(offchainHint), Assert.True(s.Driver.PageSource.Contains(offchainHint),
"Lightning hint should be present at this point"); "Lightning hint should be present at this point");
// verify steps for wallet setup are displayed correctly
s.GoToStore(StoreNavPages.Dashboard);
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-StoreDone")).Displayed);
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-Wallet")).Displayed);
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-Lightning")).Displayed);
// setup onchain wallet // setup onchain wallet
s.GoToStore(); s.Driver.FindElement(By.Id("SetupGuide-Wallet")).Click();
Thread.Sleep(10000);
s.AddDerivationScheme(); s.AddDerivationScheme();
s.Driver.AssertNoError(); s.Driver.AssertNoError();
Assert.False(s.Driver.PageSource.Contains(onchainHint), Assert.False(s.Driver.PageSource.Contains(onchainHint),
"Wallet hint not dismissed on derivation scheme add"); "Wallet hint not dismissed on derivation scheme add");
s.GoToStore(StoreNavPages.Dashboard);
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-WalletDone")).Displayed);
// setup offchain wallet // setup offchain wallet
s.GoToStore(); s.Driver.FindElement(By.Id("SetupGuide-Lightning")).Click();
s.AddLightningNode(); s.AddLightningNode();
s.Driver.AssertNoError(); s.Driver.AssertNoError();
var successAlert = s.FindAlertMessage(); var successAlert = s.FindAlertMessage();
@@ -421,8 +455,11 @@ namespace BTCPayServer.Tests
Assert.False(s.Driver.PageSource.Contains(offchainHint), Assert.False(s.Driver.PageSource.Contains(offchainHint),
"Lightning hint should be dismissed at this point"); "Lightning hint should be dismissed at this point");
var storeUrl = s.Driver.Url;
s.ClickOnAllSectionLinks(); s.ClickOnAllSectionLinks();
s.GoToStore(StoreNavPages.Dashboard);
Assert.True(s.Driver.FindElement(By.Id("SetupGuide-LightningDone")).Displayed);
s.GoToInvoices(); s.GoToInvoices();
Assert.Contains("There are no invoices matching your criteria.", s.Driver.PageSource); Assert.Contains("There are no invoices matching your criteria.", s.Driver.PageSource);
var invoiceId = s.CreateInvoice(); var invoiceId = s.CreateInvoice();
@@ -466,7 +503,7 @@ namespace BTCPayServer.Tests
// When logout out we should not be able to access store and invoice details // When logout out we should not be able to access store and invoice details
s.Logout(); s.Logout();
s.Driver.Navigate().GoToUrl(storeUrl); s.GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url); Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl); s.Driver.Navigate().GoToUrl(invoiceUrl);
Assert.Contains("ReturnUrl", s.Driver.Url); Assert.Contains("ReturnUrl", s.Driver.Url);
@@ -474,7 +511,7 @@ namespace BTCPayServer.Tests
// When logged in as different user we should not be able to access store and invoice details // When logged in as different user we should not be able to access store and invoice details
var bob = s.RegisterNewUser(); var bob = s.RegisterNewUser();
s.Driver.Navigate().GoToUrl(storeUrl); s.GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url); Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl); s.Driver.Navigate().GoToUrl(invoiceUrl);
s.AssertAccessDenied(); s.AssertAccessDenied();
@@ -483,16 +520,16 @@ namespace BTCPayServer.Tests
// Let's add Bob as a guest to alice's store // Let's add Bob as a guest to alice's store
s.LogIn(alice); s.LogIn(alice);
s.Driver.Navigate().GoToUrl(storeUrl + "/users"); s.GoToUrl(storeUrl + "/users");
s.Driver.FindElement(By.Id("Email")).SendKeys(bob + Keys.Enter); s.Driver.FindElement(By.Id("Email")).SendKeys(bob + Keys.Enter);
Assert.Contains("User added successfully", s.Driver.PageSource); Assert.Contains("User added successfully", s.Driver.PageSource);
s.Logout(); s.Logout();
// Bob should not have access to store, but should have access to invoice // Bob should not have access to store, but should have access to invoice
s.LogIn(bob); s.LogIn(bob);
s.Driver.Navigate().GoToUrl(storeUrl); s.GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url); Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl); s.GoToUrl(invoiceUrl);
s.Driver.AssertNoError(); s.Driver.AssertNoError();
// Alice should be able to delete the store // Alice should be able to delete the store
@@ -502,7 +539,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("DeleteStore")).Click(); s.Driver.FindElement(By.Id("DeleteStore")).Click();
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE"); s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
s.Driver.FindElement(By.Id("ConfirmContinue")).Click(); s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
s.Driver.Navigate().GoToUrl(storeUrl); s.GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url); Assert.Contains("ReturnUrl", s.Driver.Url);
} }
} }

View File

@@ -26,6 +26,30 @@
@if (Model.Store != null) @if (Model.Store != null)
{ {
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings"> <div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item">
<a asp-area="" asp-controller="Stores" asp-action="Dashboard" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.Dashboard)" id="StoreNav-Dashboard">
<vc:icon symbol="home"/>
<span>Dashboard</span>
</a>
</li>
<li class="nav-item">
<a asp-area="" asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.PaymentMethods) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.GeneralSettings) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Integrations) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
<vc:icon symbol="settings"/>
<span>Settings</span>
</a>
</li>
</ul>
</div>
</div>
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<header class="accordion-header" id="Nav-Wallets-Header">
<div class="accordion-button">
Wallets
</div>
</header>
<div id="Nav-Wallets" class="accordion-collapse" aria-labelledby="Nav-Wallets-Header">
<div class="accordion-body"> <div class="accordion-body">
<ul class="navbar-nav"> <ul class="navbar-nav">
@foreach (var scheme in Model.DerivationSchemes.OrderBy(scheme => scheme.Collapsed)) @foreach (var scheme in Model.DerivationSchemes.OrderBy(scheme => scheme.Collapsed))
@@ -95,19 +119,19 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a asp-area="" asp-controller="StorePullPayments" asp-action="PullPayments" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" id="StoreNav-@(nameof(StoreNavPages.PullPayments))"> <a asp-area="" asp-controller="StorePullPayments" asp-action="PullPayments" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" id="StoreNav-PullPayments">
<vc:icon symbol="payment-2"/> <vc:icon symbol="payment-2"/>
<span>Pull Payments</span> <span>Pull Payments</span>
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a asp-area="" asp-controller="StorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-@(nameof(StoreNavPages.Payouts))"> <a asp-area="" asp-controller="StorePullPayments" asp-action="Payouts" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" id="StoreNav-Payouts">
<vc:icon symbol="payment-2"/> <vc:icon symbol="payment-2"/>
<span>Payouts</span> <span>Payouts</span>
</a> </a>
</li> </li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings"> <li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" id="StoreNav-@(nameof(StoreNavPages.PayButton))"> <a asp-area="" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" id="StoreNav-PayButton">
<vc:icon symbol="payment-2"/> <vc:icon symbol="payment-2"/>
<span>Pay Button</span> <span>Pay Button</span>
</a> </a>
@@ -116,6 +140,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings"> <div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<header class="accordion-header" id="Nav-Apps-Header"> <header class="accordion-header" id="Nav-Apps-Header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Apps" aria-expanded="true" aria-controls="Nav-Apps"> <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Apps" aria-expanded="true" aria-controls="Nav-Apps">
@@ -167,26 +192,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<header class="accordion-header" id="Nav-Manage-Header">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#Nav-Manage" aria-expanded="true" aria-controls="Nav-Manage">
Manage
<vc:icon symbol="caret-down"/>
</button>
</header>
<div id="Nav-Manage" class="accordion-collapse collapse show" aria-labelledby="Nav-Manage-Header">
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item">
<a asp-area="" asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Model.Store.Id" class="nav-link js-scroll-trigger @ViewData.IsActivePage(StoreNavPages.PaymentMethods) @ViewData.IsActivePage(StoreNavPages.Rates) @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance) @ViewData.IsActivePage(StoreNavPages.GeneralSettings) @ViewData.IsActivePage(StoreNavPages.Tokens) @ViewData.IsActivePage(StoreNavPages.Users) @ViewData.IsActivePage(StoreNavPages.Webhooks)" id="StoreNav-StoreSettings">
<vc:icon symbol="settings"/>
<span>Store Settings</span>
</a>
</li>
</ul>
</div>
</div>
</div>
} }
<script> <script>
const navCollapsed = window.localStorage.getItem('btcpay-nav-collapsed') const navCollapsed = window.localStorage.getItem('btcpay-nav-collapsed')

View File

@@ -1,9 +1,6 @@
@inject UserManager<ApplicationUser> UserManager
@inject ISettingsRepository SettingsRepository
@using BTCPayServer.Views.Notifications @using BTCPayServer.Views.Notifications
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Contracts @model BTCPayServer.Components.Notifications.NotificationsViewModel
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
<div id="Notifications"> <div id="Notifications">
@@ -20,22 +17,7 @@
<button class="btn btn-link p-0" type="submit">Mark all as seen</button> <button class="btn btn-link p-0" type="submit">Mark all as seen</button>
</form> </form>
</div> </div>
@foreach (var notif in Model.Last5) <partial name="Components/Notifications/List" model="Model"/>
{
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="notification d-flex align-items-center dropdown-item border-bottom border-light py-3 px-4">
<div class="me-3">
<vc:icon symbol="note" />
</div>
<div class="notification-item__content">
<div class="text-start text-wrap">
@notif.Body
</div>
<div class="text-start d-flex">
<small class="text-muted" data-timeago-unixms="@notif.Created.ToUnixTimeMilliseconds()">@notif.Created.ToTimeAgo()</small>
</div>
</div>
</a>
}
<div class="p-3"> <div class="p-3">
<a asp-controller="Notifications" asp-action="Index">View all</a> <a asp-controller="Notifications" asp-action="Index">View all</a>
</div> </div>

View File

@@ -0,0 +1,22 @@
@using BTCPayServer.Abstractions.Extensions
@model BTCPayServer.Components.Notifications.NotificationsViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
<div id="NotificationsList">
@foreach (var n in Model.Last5)
{
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@n.Id" class="notification d-flex align-items-center dropdown-item border-bottom border-light py-3 px-4">
<div class="me-3">
<vc:icon symbol="note" />
</div>
<div class="notification-item__content">
<div class="text-start text-wrap">
@n.Body
</div>
<div class="text-start d-flex">
<small class="text-muted" data-timeago-unixms="@n.Created.ToUnixTimeMilliseconds()">@n.Created.ToTimeAgo()</small>
</div>
</div>
</a>
}
</div>

View File

@@ -0,0 +1,26 @@
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Notifications;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Components.Notifications
{
public class Notifications : ViewComponent
{
private readonly NotificationManager _notificationManager;
private static readonly string[] _views = { "List", "Dropdown", "Recent" };
public Notifications(NotificationManager notificationManager)
{
_notificationManager = notificationManager;
}
public async Task<IViewComponentResult> InvokeAsync(string appearance)
{
var vm = await _notificationManager.GetSummaryNotifications(UserClaimsPrincipal);
var viewName = _views.Contains(appearance) ? appearance : _views[0];
return View(viewName, vm);
}
}
}

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Components.NotificationsDropdown namespace BTCPayServer.Components.Notifications
{ {
public class NotificationSummaryViewModel public class NotificationsViewModel
{ {
public int UnseenCount { get; set; } public int UnseenCount { get; set; }
public List<NotificationViewModel> Last5 { get; set; } public List<NotificationViewModel> Last5 { get; set; }

View File

@@ -0,0 +1,21 @@
@model BTCPayServer.Components.Notifications.NotificationsViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
<div id="NotificationsRecent">
@if (Model.Last5.Any())
{
<div class="d-flex align-items-center justify-content-between mb-3">
<h4 class="mb-0">Recent Notifications</h4>
<a asp-controller="Notifications" asp-action="Index">View all</a>
</div>
<partial name="Components/Notifications/List" model="Model"/>
}
else
{
<h4 class="mb-3">Notifications</h4>
<p class="text-secondary mt-3">
There are no recent unseen notifications.
</p>
}
</div>

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Services.Notifications;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Components.NotificationsDropdown
{
public class NotificationsDropdown : ViewComponent
{
private readonly NotificationManager _notificationManager;
public NotificationsDropdown(NotificationManager notificationManager)
{
_notificationManager = notificationManager;
}
public async Task<IViewComponentResult> InvokeAsync()
{
return View(await _notificationManager.GetSummaryNotifications(UserClaimsPrincipal));
}
}
}

View File

@@ -2,6 +2,8 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
<div id="StoreSelector"> <div id="StoreSelector">
@if (Model.Options.Count > 0)
{
<div id="StoreSelectorDropdown" class="dropdown only-for-js"> <div id="StoreSelectorDropdown" class="dropdown only-for-js">
<button id="StoreSelectorToggle" class="btn btn-secondary dropdown-toggle rounded-pill @(Model.CurrentStoreId == null ? "text-secondary" : "")" type="button" data-bs-toggle="dropdown" aria-expanded="false">@(Model.CurrentStoreId == null ? "Select Store" : Model.CurrentDisplayName)</button> <button id="StoreSelectorToggle" class="btn btn-secondary dropdown-toggle rounded-pill @(Model.CurrentStoreId == null ? "text-secondary" : "")" type="button" data-bs-toggle="dropdown" aria-expanded="false">@(Model.CurrentStoreId == null ? "Select Store" : Model.CurrentDisplayName)</button>
<ul id="StoreSelectorMenu" class="dropdown-menu" aria-labelledby="StoreSelectorToggle"> <ul id="StoreSelectorMenu" class="dropdown-menu" aria-labelledby="StoreSelectorToggle">
@@ -14,16 +16,21 @@
} }
else else
{ {
<a asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@option.Text</a> <a asp-controller="Stores" asp-action="Dashboard" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@option.Text</a>
} }
</li> </li>
} }
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a asp-controller="UserStores" asp-action="CreateStore" class="dropdown-item" id="StoreSelectorMenuItem-Create">Create Store</a></li> <li><a asp-controller="UserStores" asp-action="CreateStore" class="dropdown-item" id="StoreSelectorCreate">Create Store</a></li>
</ul> </ul>
</div> </div>
<noscript> <noscript>
<span class="h5 mb-0 me-2">@Model.CurrentDisplayName</span> <span class="h5 mb-0 me-2">@Model.CurrentDisplayName</span>
<a asp-controller="UserStores" asp-action="ListStores">Stores</a> <a asp-controller="UserStores" asp-action="ListStores">Stores</a>
</noscript> </noscript>
}
else
{
<a asp-controller="UserStores" asp-action="CreateStore" class="btn btn-primary w-100 rounded-pill" id="StoreSelectorCreate">Create Store</a>
}
</div> </div>

View File

@@ -8,11 +8,14 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Components.StoreSelector;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Filters; using BTCPayServer.Filters;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Security; using BTCPayServer.Security;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
@@ -38,6 +41,7 @@ namespace BTCPayServer.Controllers
private readonly ISettingsRepository _settingsRepository; private readonly ISettingsRepository _settingsRepository;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly IFileProvider _fileProvider; private readonly IFileProvider _fileProvider;
private readonly BTCPayNetworkProvider _networkProvider;
private IHttpClientFactory HttpClientFactory { get; } private IHttpClientFactory HttpClientFactory { get; }
private SignInManager<ApplicationUser> SignInManager { get; } private SignInManager<ApplicationUser> SignInManager { get; }
public LanguageService LanguageService { get; } public LanguageService LanguageService { get; }
@@ -47,11 +51,13 @@ namespace BTCPayServer.Controllers
IWebHostEnvironment webHostEnvironment, IWebHostEnvironment webHostEnvironment,
LanguageService languageService, LanguageService languageService,
StoreRepository storeRepository, StoreRepository storeRepository,
BTCPayNetworkProvider networkProvider,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager)
{ {
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
HttpClientFactory = httpClientFactory; HttpClientFactory = httpClientFactory;
LanguageService = languageService; LanguageService = languageService;
_networkProvider = networkProvider;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_fileProvider = webHostEnvironment.WebRootFileProvider; _fileProvider = webHostEnvironment.WebRootFileProvider;
SignInManager = signInManager; SignInManager = signInManager;
@@ -68,17 +74,25 @@ namespace BTCPayServer.Controllers
if (SignInManager.IsSignedIn(User)) if (SignInManager.IsSignedIn(User))
{ {
var userId = SignInManager.UserManager.GetUserId(HttpContext.User);
var storeId = HttpContext.GetUserPrefsCookie()?.CurrentStoreId; var storeId = HttpContext.GetUserPrefsCookie()?.CurrentStoreId;
if (storeId != null) if (storeId != null)
{ {
var userId = SignInManager.UserManager.GetUserId(HttpContext.User);
var store = await _storeRepository.FindStore(storeId, userId); var store = await _storeRepository.FindStore(storeId, userId);
if (store != null) if (store != null)
{ {
HttpContext.SetStoreData(store); HttpContext.SetStoreData(store);
} }
} }
return View("Home");
var stores = await _storeRepository.GetStoresByUserId(userId);
var vm = new HomeViewModel
{
HasStore = stores.Any()
};
return View("Home", vm);
} }
return Challenge(); return Challenge();
@@ -91,7 +105,6 @@ namespace BTCPayServer.Controllers
return Json(LanguageService.GetLanguages(), new JsonSerializerSettings { Formatting = Formatting.Indented }); return Json(LanguageService.GetLanguages(), new JsonSerializerSettings { Formatting = Formatting.Indented });
} }
[Route("misc/permissions")] [Route("misc/permissions")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie + "," + AuthenticationSchemes.Greenfield)]
public IActionResult Permissions() public IActionResult Permissions()

View File

@@ -137,6 +137,29 @@ namespace BTCPayServer.Controllers
} }
} }
[HttpGet("{storeId}")]
public IActionResult Dashboard()
{
var store = CurrentStore;
var storeBlob = store.GetStoreBlob();
AddPaymentMethods(store, storeBlob,
out var derivationSchemes, out var lightningNodes);
var vm = new StoreDashboardViewModel
{
#if ALTCOINS
AltcoinsBuild = true,
#endif
WalletEnabled = derivationSchemes.Any(scheme => !string.IsNullOrEmpty(scheme.Value) && scheme.Enabled),
LightningEnabled = lightningNodes.Any(ln => !string.IsNullOrEmpty(ln.Address) && ln.Enabled),
StoreId = CurrentStore.Id,
StoreName = CurrentStore.StoreName
};
return View("Dashboard", vm);
}
[HttpPost] [HttpPost]
[Route("{storeId}/users")] [Route("{storeId}/users")]
public async Task<IActionResult> StoreUsers(StoreUsersViewModel vm) public async Task<IActionResult> StoreUsers(StoreUsersViewModel vm)
@@ -562,7 +585,7 @@ namespace BTCPayServer.Controllers
} }
} }
[HttpGet("{storeId}")] [HttpGet("{storeId}/payment-methods")]
public IActionResult PaymentMethods() public IActionResult PaymentMethods()
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
@@ -591,7 +614,7 @@ namespace BTCPayServer.Controllers
return View(vm); return View(vm);
} }
[HttpPost("{storeId}")] [HttpPost("{storeId}/payment-methods")]
public async Task<IActionResult> PaymentMethods(PaymentMethodsViewModel model, string command = null) public async Task<IActionResult> PaymentMethods(PaymentMethodsViewModel model, string command = null)
{ {
bool needUpdate = false; bool needUpdate = false;

View File

@@ -47,7 +47,7 @@ namespace BTCPayServer.Controllers
var store = await _repo.CreateStore(GetUserId(), vm.Name); var store = await _repo.CreateStore(GetUserId(), vm.Name);
CreatedStoreId = store.Id; CreatedStoreId = store.Id;
TempData[WellKnownTempData.SuccessMessage] = "Store successfully created"; TempData[WellKnownTempData.SuccessMessage] = "Store successfully created";
return RedirectToAction(nameof(StoresController.PaymentMethods), "Stores", new return RedirectToAction(nameof(StoresController.Dashboard), "Stores", new
{ {
storeId = store.Id storeId = store.Id
}); });
@@ -82,7 +82,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet] [HttpGet]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewStoreSettings)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettingsUnscoped)]
public async Task<IActionResult> ListStores( public async Task<IActionResult> ListStores(
string sortOrder = null, string sortOrder = null,
string sortOrderColumn = null string sortOrderColumn = null

View File

@@ -0,0 +1,6 @@
namespace BTCPayServer.Models;
public class HomeViewModel
{
public bool HasStore { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace BTCPayServer.Models.StoreViewModels;
public class StoreDashboardViewModel
{
public string StoreId { get; set; }
public string StoreName { get; set; }
public bool WalletEnabled { get; set; }
public bool LightningEnabled { get; set; }
public bool AltcoinsBuild { get; set; }
}

View File

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

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Components.NotificationsDropdown; using BTCPayServer.Components.Notifications;
using BTCPayServer.Data; using BTCPayServer.Data;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@@ -33,7 +33,7 @@ namespace BTCPayServer.Services.Notifications
private const int _cacheExpiryMs = 5000; private const int _cacheExpiryMs = 5000;
public async Task<NotificationSummaryViewModel> GetSummaryNotifications(ClaimsPrincipal user) public async Task<NotificationsViewModel> GetSummaryNotifications(ClaimsPrincipal user)
{ {
var userId = _userManager.GetUserId(user); var userId = _userManager.GetUserId(user);
var cacheKey = GetNotificationsCacheId(userId); var cacheKey = GetNotificationsCacheId(userId);
@@ -48,7 +48,7 @@ namespace BTCPayServer.Services.Notifications
UserId = userId UserId = userId
}); });
entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(_cacheExpiryMs)); entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(_cacheExpiryMs));
var res = new NotificationSummaryViewModel() { Last5 = resp.Items, UnseenCount = resp.Count }; var res = new NotificationsViewModel { Last5 = resp.Items, UnseenCount = resp.Count };
entry.Value = res; entry.Value = res;
return res; return res;
}); });

View File

@@ -1,84 +1,60 @@
@{ @model HomeViewModel;
ViewData["Title"] = "Home Page"; @{
ViewData["Title"] = "BTCPay Server";
} }
<div class="container"> @section PageHeadContent
{
<style>
.list-group-item .image {
display: flex;
flex: 0 0 1rem;
align-items: center;
justify-content: center;
}
.list-group-item .icon {
width: 1.5rem;
height: 1.5rem;
margin: 1rem;
}
.list-group-item .content {
flex: 1;
padding: 1rem 0;
}
#NotificationsRecent .notification {
padding: var(--btcpay-space-s) var(--btcpay-space-m) !important;
}
</style>
}
<h2>Welcome to your BTCPay Server</h2>
@if (Model.HasStore)
{
<div class="row"> <div class="row">
<div class="col-lg-12 text-center"> <div class="col-lg-8 col-xl-6 pt-3">
<div> <vc:notifications appearance="Recent" />
<h1>Welcome to BTCPay Server</h1>
<p class="lead" style="max-width:30em;margin:1.5em auto">BTCPay Server is a free and open source server for merchants wanting to accept Bitcoin for their business.</p>
<a href="https://btcpayserver.org" target="_blank" rel="noreferrer noopener" class="btn btn-primary rounded-pill fs-5">Official website</a>
</div>
<hr class="primary my-5">
<h2 class="mb-4">A Payment Server for Bitcoin</h2>
</div> </div>
</div> </div>
}
else
{
<div class="row"> <div class="row">
<div class="col-lg-4 col-md-6 text-center"> <div class="col-lg-9 col-xl-6">
<div class="py-4 service-box"> <p class="lead text-secondary">To start accepting payments, set up a store and a wallet.</p>
<img src="~/img/lock-logo.png" class="mb-2" alt="" asp-append-version="true" />
<h3>Secure</h3> <div class="list-group mt-4" id="SetupGuide">
<p class="text-muted mb-0">The payment server does not need to know your private keys, so your money can't be stolen.</p> <a asp-controller="UserStores" asp-action="CreateStore" id="SetupGuide-Store" class="list-group-item list-group-item-action d-flex align-items-center">
<vc:icon symbol="new-store"/>
<div class="content">
<h5 class="mb-0">Create your store</h5>
</div> </div>
</div> <vc:icon symbol="caret-right"/>
<div class="col-lg-4 col-md-6 text-center">
<div class="py-4 service-box">
<img src="~/img/qr-logo.png" class="mb-2" alt="" asp-append-version="true" />
<h3>Easy</h3>
<p class="text-muted mb-0">A user-friendly Bitcoin checkout page for your customers.</p>
</div>
</div>
<div class="col-lg-4 col-md-6 text-center">
<div class="py-4 service-box">
<img src="~/img/money-logo.png" class="mb-2" alt="" asp-append-version="true" />
<h3>Visibility</h3>
<p class="text-muted mb-0">Manage, generate reports, and search for your invoices easily.</p>
</div>
</div>
</div>
<div class="row text-center my-5">
<h2 class="mb-4">Video tutorials</h2>
<div class="text-center">
<a href="https://www.youtube.com/channel/UCpG9WL6TJuoNfFVkaDMp9ug" target="_blank" rel="noreferrer noopener">
<img src="~/img/youtube.png" alt="Video tutorials" class="img-fluid" asp-append-version="true" />
</a> </a>
</div> </div>
</div> </div>
<div class="row">
<div class="col-lg-8 mx-auto text-center">
<h2>Donate</h2>
<hr class="primary">
<p>
BTCPay Server is proudly free and open-source, built and maintained<br /> by a world-wide community of passionate contributors.<br />
Support us by donating through BTCPay Server Foundation<br /> or by directly sending donation to a specific contributor.
</p>
<p>
<a href="https://btcpayserver.org/donate/" rel="noreferrer noopener">
<svg viewBox="0 0 208 55" width="208" xmlns="http://www.w3.org/2000/svg">
<path d="m208 48c0 3.866-3.135 7-7 7h-194c-3.866 0-7-3.134-7-7v-41c0-3.866 3.134-7 7-7h194c3.865 0 7 3.134 7 7z" fill="var(--btcpay-bg-cta, #0f3723)"/>
<g fill="#fff">
<path d="m125.169 20.648c.519 0 1.017.051 1.495.153.479.103.898.276 1.261.522s.654.57.873.972c.22.402.329.903.329 1.503 0 .336-.052.664-.156.981-.104.318-.25.606-.439.864-.19.258-.415.477-.674.657-.26.18-.551.306-.873.378v.036c.793.108 1.428.447 1.899 1.017.474.57.708 1.275.708 2.115 0 .204-.017.436-.052.693s-.104.521-.207.792c-.104.271-.254.537-.45.801-.195.265-.458.495-.785.693-.328.198-.731.36-1.209.485-.479.127-1.052.189-1.72.189h-4.368v-12.851zm0 5.634c.472 0 .881-.057 1.228-.171.346-.114.633-.27.863-.468s.403-.429.518-.693.174-.546.174-.846c0-1.608-.928-2.412-2.782-2.412h-3.192v4.59zm0 6.174c.438 0 .853-.039 1.244-.117s.737-.219 1.037-.423c.299-.204.536-.477.708-.818.173-.343.259-.771.259-1.287 0-.828-.278-1.449-.838-1.863s-1.362-.621-2.41-.621h-3.192v5.13h3.192z"/>
<path d="m137.534 20.648v1.044h-3.167v11.808h-1.176v-11.808h-3.15v-1.044z"/>
<path d="m146.747 23.301c-.231-.381-.513-.7-.845-.958s-.697-.454-1.096-.589c-.4-.135-.82-.202-1.259-.202-.799 0-1.485.158-2.06.475-.574.316-1.045.736-1.41 1.257-.366.522-.637 1.111-.811 1.768-.176.657-.263 1.332-.263 2.023 0 .68.087 1.351.263 2.013.174.663.443 1.255.811 1.777.365.521.835.941 1.41 1.258.574.316 1.261.475 2.06.475.563 0 1.069-.104 1.521-.315.451-.212.838-.499 1.166-.862.326-.363.591-.789.793-1.275.203-.485.332-1.011.388-1.574h1.147c-.078.774-.252 1.473-.522 2.094-.271.622-.619 1.148-1.047 1.583-.427.435-.931.769-1.511 1.002s-1.225.353-1.934.353c-.945 0-1.775-.18-2.49-.537-.716-.358-1.31-.836-1.781-1.435-.473-.599-.828-1.29-1.064-2.075s-.354-1.613-.354-2.479c0-.868.117-1.694.354-2.48.236-.785.592-1.479 1.064-2.084.473-.604 1.065-1.084 1.781-1.442.715-.357 1.545-.537 2.49-.537.574 0 1.135.088 1.68.264s1.041.437 1.486.783c.444.346.816.771 1.114 1.275s.487 1.085.565 1.741h-1.147c-.101-.485-.267-.916-.499-1.297z"/>
<path d="m154.704 20.648c.553 0 1.054.081 1.503.243s.835.402 1.158.72c.322.318.57.705.742 1.161.174.456.26.972.26 1.548s-.086 1.943-.26 2.399c-.172.456-.42.843-.742 1.161s-.708.558-1.158.72-.95.243-1.503.243h-2.884v4.657h-1.176v-12.852zm-.259 7.151c.818 0 1.479-.216 1.986-.648.508-.432.762-1.943.762-2.831s-.254-1.548-.762-1.98c-.507-.432-1.168-.648-1.986-.648h-2.625v6.107z"/>
<path d="m164.361 20.648 4.84 12.852h-1.262l-1.504-3.996h-5.615l-1.486 3.996h-1.244l4.959-12.852zm1.677 7.812-2.384-6.588-2.473 6.588z"/>
<path d="m167.398 20.648h1.381l4.06 6.516 4.043-6.516h1.399l-4.872 7.56v5.292h-1.177v-5.292z"/>
<path d="m27.005 22.02c.782-.118 1.711-.207 2.729-.207 1.844 0 3.158.428 4.028 1.24.885.812 1.402 1.962 1.402 3.571 0 1.623-.502 2.951-1.432 3.866-.929.93-2.464 1.432-4.397 1.432-.915 0-1.682-.045-2.331-.118v-9.784zm1.284 8.793c.324.06.796.074 1.298.074 2.745 0 4.235-1.534 4.235-4.221.015-2.346-1.313-3.836-4.028-3.836-.664 0-1.166.059-1.505.133z"/>
<path d="m43.312 28.202c0 2.642-1.83 3.792-3.556 3.792-1.933 0-3.423-1.416-3.423-3.674 0-2.391 1.564-3.792 3.542-3.792 2.05 0 3.437 1.49 3.437 3.674zm-5.666.073c0 1.564.9 2.745 2.169 2.745 1.239 0 2.169-1.166 2.169-2.774 0-1.21-.605-2.745-2.14-2.745-1.533 0-2.198 1.417-2.198 2.774z"/>
<path d="m44.953 26.623c0-.738-.015-1.343-.059-1.933h1.151l.074 1.181h.03c.354-.679 1.18-1.343 2.361-1.343.988 0 2.523.59 2.523 3.04v4.265h-1.298v-4.117c0-1.151-.428-2.11-1.653-2.11-.855 0-1.52.605-1.741 1.328-.059.163-.089.384-.089.605v4.294h-1.298v-5.21z"/>
<path d="m57.099 31.832-.104-.9h-.044c-.398.561-1.166 1.062-2.184 1.062-1.446 0-2.184-1.018-2.184-2.051 0-1.727 1.534-2.671 4.293-2.656v-.148c0-.59-.162-1.652-1.623-1.652-.664 0-1.357.207-1.859.531l-.294-.856c.59-.384 1.446-.634 2.346-.634 2.184 0 2.715 1.49 2.715 2.921v2.671c0 .62.03 1.225.118 1.712zm-.192-3.644c-1.417-.029-3.025.221-3.025 1.608 0 .841.561 1.239 1.225 1.239.93 0 1.52-.59 1.727-1.195.044-.133.074-.28.074-.413v-1.239z"/>
<path d="m61.764 22.639v2.051h1.859v.989h-1.859v3.852c0 .886.251 1.387.974 1.387.339 0 .59-.044.752-.088l.059.974c-.251.104-.649.177-1.151.177-.605 0-1.092-.191-1.402-.546-.369-.384-.502-1.018-.502-1.859v-3.896h-1.106v-.99h1.106v-1.711z"/>
<path d="m65.72 28.497c.029 1.756 1.151 2.479 2.449 2.479.93 0 1.49-.163 1.977-.369l.222.93c-.458.206-1.24.442-2.376.442-2.198 0-3.512-1.446-3.512-3.601s1.269-3.851 3.35-3.851c2.332 0 2.951 2.051 2.951 3.364 0 .266-.03.472-.044.605h-5.017zm3.806-.93c.015-.826-.339-2.11-1.8-2.11-1.313 0-1.889 1.209-1.992 2.11z"/>
<path d="m77.084 22.639v2.051h1.859v.989h-1.859v3.852c0 .886.251 1.387.974 1.387.339 0 .59-.044.752-.088l.059.974c-.251.104-.649.177-1.151.177-.605 0-1.092-.191-1.402-.546-.369-.384-.502-1.018-.502-1.859v-3.896h-1.106v-.99h1.106v-1.711z"/>
<path d="m86.78 28.202c0 2.642-1.83 3.792-3.556 3.792-1.933 0-3.423-1.416-3.423-3.674 0-2.391 1.564-3.792 3.542-3.792 2.05 0 3.437 1.49 3.437 3.674zm-5.666.073c0 1.564.9 2.745 2.169 2.745 1.239 0 2.169-1.166 2.169-2.774 0-1.21-.605-2.745-2.14-2.745-1.534 0-2.198 1.417-2.198 2.774z"/>
</g>
<path d="m98.329 22.548v-8.32l10.112 4.812-7.447 5.435 3.511 2.537 8.67-6.263c.888-.685 1.47-1.238 1.421-2.137-.07-1.287-.923-1.772-1.695-2.081l-15.397-7.293s-1.7-.78-2.801.482c-.384.44-.453 1.765-.453 1.765v31.58h.029c-.036-1.295 1.166-1.9 1.166-1.9l2.884-1.372v-8.321z" fill="#cdd932"/>
<path d="m104.505 27.01-6.176-4.462v8.929z" fill="#1d7a44"/>
<path d="m113.176 33.271-8.67-6.262-3.511 2.536 7.446 5.435-10.111 4.813-2.884 1.372s-1.202.604-1.166 1.901c.009.324.093.688.294 1.101.745 1.521 2.93.615 2.93.615l15.398-7.292c.771-.309 1.625-.793 1.693-2.081.05-.899-.533-1.452-1.419-2.138z" fill="#51b13e"/>
</svg>
</a>
</p>
</div>
</div>
</div> </div>
}

View File

@@ -31,7 +31,7 @@
} }
</a> </a>
<vc:store-selector /> <vc:store-selector />
<vc:notifications-dropdown /> <vc:notifications appearance="Dropdown" />
<button id="mainMenuToggle" class="mainMenuButton" type="button" data-bs-toggle="offcanvas" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation"> <button id="mainMenuToggle" class="mainMenuButton" type="button" data-bs-toggle="offcanvas" data-bs-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span>Menu</span> <span>Menu</span>
</button> </button>

View File

@@ -0,0 +1,118 @@
@model StoreDashboardViewModel;
@{
ViewData.SetActivePage(StoreNavPages.Dashboard, Model.StoreName, Model.StoreId);
var isReady = Model.WalletEnabled || Model.LightningEnabled;
}
@section PageHeadContent
{
<style>
.list-group-item .image {
display: flex;
flex: 0 0 1rem;
align-items: center;
justify-content: center;
}
.list-group-item .icon {
width: 1.5rem;
height: 1.5rem;
margin: 1rem;
}
.list-group-item .content {
flex: 1;
padding: 1rem 0;
}
</style>
}
<partial name="_StatusMessage" />
<h2>@ViewData["Title"]</h2>
@if (isReady)
{
<p class="lead text-secondary">This store is ready to accept transactions, good job!</p>
}
else
{
<p class="lead text-secondary">To start accepting payments, set up a wallet or a Lightning node.</p>
}
<div class="row">
<div class="col-md-7 col-xl-5">
<div class="list-group" id="SetupGuide">
<div class="list-group-item d-flex align-items-center" id="SetupGuide-StoreDone">
<vc:icon symbol="done"/>
<div class="content">
<h5 class="mb-0 text-success">Create your store</h5>
</div>
</div>
@if (!Model.WalletEnabled)
{
if (!Model.AltcoinsBuild)
{
<a asp-controller="Stores" asp-action="SetupWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="BTC" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/>
<div class="content">
<h5 class="mb-0">Set up a wallet</h5>
</div>
<vc:icon symbol="caret-right"/>
</a>
}
else
{
<a asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Model.StoreId" id="SetupGuide-Wallet" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/>
<div class="content">
<h5 class="mb-0">Set up a wallet</h5>
</div>
<vc:icon symbol="caret-right"/>
</a>
}
}
else
{
<div class="list-group-item d-flex align-items-center" id="SetupGuide-WalletDone">
<vc:icon symbol="done"/>
<div class="content">
<h5 class="mb-0 text-success">Set up a wallet</h5>
</div>
</div>
}
@if (!Model.LightningEnabled)
{
if (!Model.AltcoinsBuild)
{
<a asp-controller="Stores" asp-action="SetupLightningNode" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="BTC" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/>
<div class="content">
<h5 class="mb-0">Set up a Lightning node</h5>
</div>
<vc:icon symbol="caret-right"/>
</a>
}
else
{
<a asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Model.StoreId" id="SetupGuide-Lightning" class="list-group-item list-group-item-action d-flex align-items-center order-1">
<vc:icon symbol="new-wallet"/>
<div class="content">
<h5 class="mb-0">Set up a Lightning node</h5>
</div>
<vc:icon symbol="caret-right"/>
</a>
}
}
else
{
<div class="list-group-item d-flex align-items-center" id="SetupGuide-LightningDone">
<vc:icon symbol="done"/>
<div class="content">
<h5 class="mb-0 text-success">Set up a Lightning node</h5>
</div>
</div>
}
</div>
</div>
</div>

View File

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

View File

@@ -21,7 +21,6 @@
</div> </div>
<div class="form-group mt-4"> <div class="form-group mt-4">
<input type="submit" value="Create" class="btn btn-primary" id="Create" /> <input type="submit" value="Create" class="btn btn-primary" id="Create" />
<a asp-action="ListStores" class="btn btn-link px-0 ms-3">Back to list</a>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -5,6 +5,7 @@
<symbol id="close" viewBox="0 0 16 16"><path d="M9.38526 8.08753L15.5498 1.85558C15.9653 1.43545 15.9653 0.805252 15.5498 0.385121C15.1342 -0.0350102 14.5108 -0.0350102 14.0952 0.385121L7.93072 6.61707L1.76623 0.315098C1.35065 -0.105033 0.727273 -0.105033 0.311688 0.315098C-0.103896 0.73523 -0.103896 1.36543 0.311688 1.78556L6.47618 8.0175L0.311688 14.2495C-0.103896 14.6696 -0.103896 15.2998 0.311688 15.7199C0.519481 15.93 0.796499 16 1.07355 16C1.35061 16 1.62769 15.93 1.83548 15.7199L7.99997 9.48797L14.1645 15.7199C14.3722 15.93 14.6493 16 14.9264 16C15.2034 16 15.4805 15.93 15.6883 15.7199C16.1039 15.2998 16.1039 14.6696 15.6883 14.2495L9.38526 8.08753Z" fill="currentColor"/></symbol> <symbol id="close" viewBox="0 0 16 16"><path d="M9.38526 8.08753L15.5498 1.85558C15.9653 1.43545 15.9653 0.805252 15.5498 0.385121C15.1342 -0.0350102 14.5108 -0.0350102 14.0952 0.385121L7.93072 6.61707L1.76623 0.315098C1.35065 -0.105033 0.727273 -0.105033 0.311688 0.315098C-0.103896 0.73523 -0.103896 1.36543 0.311688 1.78556L6.47618 8.0175L0.311688 14.2495C-0.103896 14.6696 -0.103896 15.2998 0.311688 15.7199C0.519481 15.93 0.796499 16 1.07355 16C1.35061 16 1.62769 15.93 1.83548 15.7199L7.99997 9.48797L14.1645 15.7199C14.3722 15.93 14.6493 16 14.9264 16C15.2034 16 15.4805 15.93 15.6883 15.7199C16.1039 15.2998 16.1039 14.6696 15.6883 14.2495L9.38526 8.08753Z" fill="currentColor"/></symbol>
<symbol id="caret-right" viewBox="0 0 24 24"><path d="M9.5 17L14.5 12L9.5 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></symbol> <symbol id="caret-right" viewBox="0 0 24 24"><path d="M9.5 17L14.5 12L9.5 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></symbol>
<symbol id="caret-down" viewBox="0 0 24 24"><path d="M7 9.5L12 14.5L17 9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></symbol> <symbol id="caret-down" viewBox="0 0 24 24"><path d="M7 9.5L12 14.5L17 9.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></symbol>
<symbol id="new-store" viewBox="0 0 32 32"><path d="M16 10V22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 16H10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle fill="none" cx="16" cy="16" r="15" stroke="currentColor" stroke-width="2"/></symbol>
<symbol id="new-wallet" viewBox="0 0 32 32"><path d="M16 10V22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 16H10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle fill="none" cx="16" cy="16" r="15" stroke="currentColor" stroke-width="2"/></symbol> <symbol id="new-wallet" viewBox="0 0 32 32"><path d="M16 10V22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 16H10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle fill="none" cx="16" cy="16" r="15" stroke="currentColor" stroke-width="2"/></symbol>
<symbol id="existing-wallet" viewBox="0 0 32 32"><g clip-path="url(#clip0)"><path d="M26.5362 7.08746H25.9614V3.25512C25.9614 2.10542 25.3865 1.14734 24.6201 0.572488C23.8536 -0.00236247 22.7039 -0.193979 21.7458 -0.00236246L4.11707 5.36291C2.00929 5.93776 0.667969 7.85392 0.667969 9.96171V12.0695V12.836V27.3988C0.667969 30.0815 2.77575 32.1893 5.45839 32.1893H26.5362C29.2189 32.1893 31.3267 30.0815 31.3267 27.3988V12.0695C31.3267 9.38686 29.2189 7.08746 26.5362 7.08746ZM4.69192 7.08746L22.129 1.91381C22.5123 1.72219 23.0871 1.91381 23.4704 2.10542C23.8536 2.29704 24.0452 2.87189 24.0452 3.25512V7.08746H5.45839C4.88354 7.08746 4.5003 7.27908 3.92545 7.47069C4.11707 7.27908 4.5003 7.08746 4.69192 7.08746ZM29.4105 27.2072C29.4105 28.7402 28.0692 30.0815 26.5362 30.0815H5.45839C3.92545 30.0815 2.58414 28.7402 2.58414 27.2072V12.836V11.8779C2.58414 10.3449 3.92545 9.00362 5.45839 9.00362H26.5362C28.0692 9.00362 29.4105 10.3449 29.4105 11.8779V27.2072Z" fill="currentColor"/><path d="M25.9591 21.6487C27.0174 21.6487 27.8753 20.7908 27.8753 19.7326C27.8753 18.6743 27.0174 17.8164 25.9591 17.8164C24.9009 17.8164 24.043 18.6743 24.043 19.7326C24.043 20.7908 24.9009 21.6487 25.9591 21.6487Z" fill="currentColor"/></g><defs><clipPath id="clip0"><rect width="32" height="32" fill="white"/></clipPath></defs></symbol> <symbol id="existing-wallet" viewBox="0 0 32 32"><g clip-path="url(#clip0)"><path d="M26.5362 7.08746H25.9614V3.25512C25.9614 2.10542 25.3865 1.14734 24.6201 0.572488C23.8536 -0.00236247 22.7039 -0.193979 21.7458 -0.00236246L4.11707 5.36291C2.00929 5.93776 0.667969 7.85392 0.667969 9.96171V12.0695V12.836V27.3988C0.667969 30.0815 2.77575 32.1893 5.45839 32.1893H26.5362C29.2189 32.1893 31.3267 30.0815 31.3267 27.3988V12.0695C31.3267 9.38686 29.2189 7.08746 26.5362 7.08746ZM4.69192 7.08746L22.129 1.91381C22.5123 1.72219 23.0871 1.91381 23.4704 2.10542C23.8536 2.29704 24.0452 2.87189 24.0452 3.25512V7.08746H5.45839C4.88354 7.08746 4.5003 7.27908 3.92545 7.47069C4.11707 7.27908 4.5003 7.08746 4.69192 7.08746ZM29.4105 27.2072C29.4105 28.7402 28.0692 30.0815 26.5362 30.0815H5.45839C3.92545 30.0815 2.58414 28.7402 2.58414 27.2072V12.836V11.8779C2.58414 10.3449 3.92545 9.00362 5.45839 9.00362H26.5362C28.0692 9.00362 29.4105 10.3449 29.4105 11.8779V27.2072Z" fill="currentColor"/><path d="M25.9591 21.6487C27.0174 21.6487 27.8753 20.7908 27.8753 19.7326C27.8753 18.6743 27.0174 17.8164 25.9591 17.8164C24.9009 17.8164 24.043 18.6743 24.043 19.7326C24.043 20.7908 24.9009 21.6487 25.9591 21.6487Z" fill="currentColor"/></g><defs><clipPath id="clip0"><rect width="32" height="32" fill="white"/></clipPath></defs></symbol>
<symbol id="hot-wallet" viewBox="0 0 32 32"><g clip-path="url(#clip0)"><path d="M26.5362 7.08746H25.9614V3.25512C25.9614 2.10542 25.3865 1.14734 24.6201 0.572488C23.8536 -0.00236247 22.7039 -0.193979 21.7458 -0.00236246L4.11707 5.36291C2.00929 5.93776 0.667969 7.85392 0.667969 9.96171V12.0695V12.836V27.3988C0.667969 30.0815 2.77575 32.1893 5.45839 32.1893H26.5362C29.2189 32.1893 31.3267 30.0815 31.3267 27.3988V12.0695C31.3267 9.38686 29.2189 7.08746 26.5362 7.08746ZM4.69192 7.08746L22.129 1.91381C22.5123 1.72219 23.0871 1.91381 23.4704 2.10542C23.8536 2.29704 24.0452 2.87189 24.0452 3.25512V7.08746H5.45839C4.88354 7.08746 4.5003 7.27908 3.92545 7.47069C4.11707 7.27908 4.5003 7.08746 4.69192 7.08746ZM29.4105 27.2072C29.4105 28.7402 28.0692 30.0815 26.5362 30.0815H5.45839C3.92545 30.0815 2.58414 28.7402 2.58414 27.2072V12.836V11.8779C2.58414 10.3449 3.92545 9.00362 5.45839 9.00362H26.5362C28.0692 9.00362 29.4105 10.3449 29.4105 11.8779V27.2072Z" fill="currentColor"/><path d="M25.9591 21.6487C27.0174 21.6487 27.8753 20.7908 27.8753 19.7326C27.8753 18.6743 27.0174 17.8164 25.9591 17.8164C24.9009 17.8164 24.043 18.6743 24.043 19.7326C24.043 20.7908 24.9009 21.6487 25.9591 21.6487Z" fill="currentColor"/></g><defs><clipPath id="clip0"><rect width="32" height="32" fill="white"/></clipPath></defs></symbol> <symbol id="hot-wallet" viewBox="0 0 32 32"><g clip-path="url(#clip0)"><path d="M26.5362 7.08746H25.9614V3.25512C25.9614 2.10542 25.3865 1.14734 24.6201 0.572488C23.8536 -0.00236247 22.7039 -0.193979 21.7458 -0.00236246L4.11707 5.36291C2.00929 5.93776 0.667969 7.85392 0.667969 9.96171V12.0695V12.836V27.3988C0.667969 30.0815 2.77575 32.1893 5.45839 32.1893H26.5362C29.2189 32.1893 31.3267 30.0815 31.3267 27.3988V12.0695C31.3267 9.38686 29.2189 7.08746 26.5362 7.08746ZM4.69192 7.08746L22.129 1.91381C22.5123 1.72219 23.0871 1.91381 23.4704 2.10542C23.8536 2.29704 24.0452 2.87189 24.0452 3.25512V7.08746H5.45839C4.88354 7.08746 4.5003 7.27908 3.92545 7.47069C4.11707 7.27908 4.5003 7.08746 4.69192 7.08746ZM29.4105 27.2072C29.4105 28.7402 28.0692 30.0815 26.5362 30.0815H5.45839C3.92545 30.0815 2.58414 28.7402 2.58414 27.2072V12.836V11.8779C2.58414 10.3449 3.92545 9.00362 5.45839 9.00362H26.5362C28.0692 9.00362 29.4105 10.3449 29.4105 11.8779V27.2072Z" fill="currentColor"/><path d="M25.9591 21.6487C27.0174 21.6487 27.8753 20.7908 27.8753 19.7326C27.8753 18.6743 27.0174 17.8164 25.9591 17.8164C24.9009 17.8164 24.043 18.6743 24.043 19.7326C24.043 20.7908 24.9009 21.6487 25.9591 21.6487Z" fill="currentColor"/></g><defs><clipPath id="clip0"><rect width="32" height="32" fill="white"/></clipPath></defs></symbol>
@@ -31,4 +32,6 @@
<symbol id="payment-2" viewBox="0 0 24 24"><path d="M12 20a8 8 0 1 1 0-16 8 8 0 0 1 0 16Zm0-15.19a7.2 7.2 0 0 0 0 14.38A7.2 7.2 0 0 0 12 4.8Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M9.48 14.85a.44.44 0 0 1-.3-.14c-.14-.16-.14-.43.05-.57l5.02-4.31c.16-.14.43-.14.57.05.14.17.14.44-.05.57l-5.05 4.29c-.05.08-.16.1-.24.1Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M14.39 14.28a.4.4 0 0 1-.41-.4l.1-3.42-3.08-.17a.4.4 0 0 1-.38-.43c0-.22.19-.4.43-.38l3.47.19c.22 0 .38.19.38.4l-.13 3.83c.02.19-.17.38-.38.38Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/></symbol> <symbol id="payment-2" viewBox="0 0 24 24"><path d="M12 20a8 8 0 1 1 0-16 8 8 0 0 1 0 16Zm0-15.19a7.2 7.2 0 0 0 0 14.38A7.2 7.2 0 0 0 12 4.8Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M9.48 14.85a.44.44 0 0 1-.3-.14c-.14-.16-.14-.43.05-.57l5.02-4.31c.16-.14.43-.14.57.05.14.17.14.44-.05.57l-5.05 4.29c-.05.08-.16.1-.24.1Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/><path d="M14.39 14.28a.4.4 0 0 1-.41-.4l.1-3.42-3.08-.17a.4.4 0 0 1-.38-.43c0-.22.19-.4.43-.38l3.47.19c.22 0 .38.19.38.4l-.13 3.83c.02.19-.17.38-.38.38Z" fill="currentColor" stroke="currentColor" stroke-width=".6"/></symbol>
<symbol id="invoice" viewBox="0 0 24 24"><path d="M17.1 20H6.9c-.83 0-1.53-.7-1.53-1.52V5.52c0-.82.7-1.52 1.52-1.52h10.22c.83 0 1.52.7 1.52 1.52v12.96c0 .82-.7 1.52-1.52 1.52ZM6.9 5.3c-.14 0-.23.1-.23.22v12.96c0 .13.1.22.22.22h10.22c.13 0 .22-.1.22-.22V5.52c0-.13-.09-.22-.22-.22H6.89Z" fill="currentColor"/><path d="M12.24 7.95H8.11c-.09 0-.13-.05-.13-.15v-1c0-.05.04-.1.09-.1h4.13c.04 0 .08.05.08.1v1c.05.1 0 .15-.04.15ZM16.2 17.6H8.1c-.08 0-.12-.08-.12-.12V9.87a.1.1 0 0 1 .09-.09h8.08a.1.1 0 0 1 .09.09v7.44c0 .11.06.3-.04.3Z" fill="currentColor"/></symbol> <symbol id="invoice" viewBox="0 0 24 24"><path d="M17.1 20H6.9c-.83 0-1.53-.7-1.53-1.52V5.52c0-.82.7-1.52 1.52-1.52h10.22c.83 0 1.52.7 1.52 1.52v12.96c0 .82-.7 1.52-1.52 1.52ZM6.9 5.3c-.14 0-.23.1-.23.22v12.96c0 .13.1.22.22.22h10.22c.13 0 .22-.1.22-.22V5.52c0-.13-.09-.22-.22-.22H6.89Z" fill="currentColor"/><path d="M12.24 7.95H8.11c-.09 0-.13-.05-.13-.15v-1c0-.05.04-.1.09-.1h4.13c.04 0 .08.05.08.1v1c.05.1 0 .15-.04.15ZM16.2 17.6H8.1c-.08 0-.12-.08-.12-.12V9.87a.1.1 0 0 1 .09-.09h8.08a.1.1 0 0 1 .09.09v7.44c0 .11.06.3-.04.3Z" fill="currentColor"/></symbol>
<symbol id="shopify" viewBox="0 0 32 32"><path transform="scale(.7) translate(5, 5)" d="m20.45 31.97 9.62-2.08-3.5-23.64c-.03-.16-.15-.26-.28-.26l-2.57-.18s-1.7-1.7-1.92-1.88a.41.41 0 0 0-.16-.1l-1.22 28.14zm-4.83-16.9s-1.09-.56-2.37-.56c-1.93 0-2 1.2-2 1.52 0 1.64 4.31 2.29 4.31 6.17 0 3.06-1.92 5.01-4.54 5.01-3.14 0-4.72-1.95-4.72-1.95l.86-2.78s1.66 1.42 3.04 1.42c.9 0 1.3-.72 1.3-1.24 0-2.16-3.54-2.26-3.54-5.81-.04-2.98 2.1-5.9 6.44-5.9 1.68 0 2.5.49 2.5.49l-1.26 3.62zM14.9 1.1c.17 0 .36.06.53.19-1.31.62-2.75 2.18-3.34 5.32-.88.28-1.73.54-2.52.77.69-2.38 2.36-6.26 5.33-6.26zm1.64 3.94v.18l-3.2.98c.63-2.37 1.79-3.53 2.79-3.96.26.67.41 1.57.41 2.8zm.72-2.98c.92.1 1.52 1.15 1.9 2.34-.46.15-.98.3-1.54.49v-.34c0-1-.13-1.82-.36-2.5zm3.99 1.72-.1.03c-.03 0-.39.1-.96.28-.56-1.65-1.56-3.16-3.34-3.16h-.16C16.2.28 15.56 0 15.02 0 10.88 0 8.9 5.17 8.28 7.8c-1.6.48-2.75.84-2.88.9-.9.28-.93.3-1.03 1.15-.1.62-2.44 18.75-2.44 18.75L20.01 32z" fill="currentColor"/></symbol> <symbol id="shopify" viewBox="0 0 32 32"><path transform="scale(.7) translate(5, 5)" d="m20.45 31.97 9.62-2.08-3.5-23.64c-.03-.16-.15-.26-.28-.26l-2.57-.18s-1.7-1.7-1.92-1.88a.41.41 0 0 0-.16-.1l-1.22 28.14zm-4.83-16.9s-1.09-.56-2.37-.56c-1.93 0-2 1.2-2 1.52 0 1.64 4.31 2.29 4.31 6.17 0 3.06-1.92 5.01-4.54 5.01-3.14 0-4.72-1.95-4.72-1.95l.86-2.78s1.66 1.42 3.04 1.42c.9 0 1.3-.72 1.3-1.24 0-2.16-3.54-2.26-3.54-5.81-.04-2.98 2.1-5.9 6.44-5.9 1.68 0 2.5.49 2.5.49l-1.26 3.62zM14.9 1.1c.17 0 .36.06.53.19-1.31.62-2.75 2.18-3.34 5.32-.88.28-1.73.54-2.52.77.69-2.38 2.36-6.26 5.33-6.26zm1.64 3.94v.18l-3.2.98c.63-2.37 1.79-3.53 2.79-3.96.26.67.41 1.57.41 2.8zm.72-2.98c.92.1 1.52 1.15 1.9 2.34-.46.15-.98.3-1.54.49v-.34c0-1-.13-1.82-.36-2.5zm3.99 1.72-.1.03c-.03 0-.39.1-.96.28-.56-1.65-1.56-3.16-3.34-3.16h-.16C16.2.28 15.56 0 15.02 0 10.88 0 8.9 5.17 8.28 7.8c-1.6.48-2.75.84-2.88.9-.9.28-.93.3-1.03 1.15-.1.62-2.44 18.75-2.44 18.75L20.01 32z" fill="currentColor"/></symbol>
<symbol id="done" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="12" fill="#51B13E"/><path d="m7 12.14 3.55 3.54L17.5 9" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></symbol>
<symbol id="home" viewBox="0 0 24 24" fill="none"><path d="M19.9257 9.5143L19.0854 6.01717C18.9094 5.29694 18.2852 4.80078 17.557 4.80078H6.44138C5.72114 4.80078 5.08894 5.30494 4.92089 6.01717L4.08062 9.5143C3.88855 10.3306 4.06461 11.1628 4.57678 11.819C4.6408 11.9071 4.72882 11.9711 4.80085 12.0511V17.6049C4.80085 18.4852 5.52108 19.2054 6.40136 19.2054H17.605C18.4853 19.2054 19.2055 18.4852 19.2055 17.6049V12.0511C19.2775 11.9791 19.3655 11.9071 19.4296 11.827C19.9417 11.1708 20.1258 10.3306 19.9257 9.5143ZM17.5329 6.39329L18.3732 9.89042C18.4532 10.2265 18.3812 10.5626 18.1732 10.8267C18.0611 10.9708 17.821 11.2028 17.4209 11.2028C16.9328 11.2028 16.5086 10.8107 16.4526 10.2905L15.9884 6.4013L17.5329 6.39329ZM12.8034 6.4013H14.3719L14.8041 10.0185C14.8441 10.3306 14.748 10.6427 14.54 10.8747C14.3639 11.0828 14.1078 11.2028 13.7797 11.2028C13.2436 11.2028 12.8034 10.7307 12.8034 10.1545V6.4013ZM9.19426 10.0185L9.6344 6.4013H11.2029V10.1545C11.2029 10.7307 10.7628 11.2028 10.1706 11.2028C9.89849 11.2028 9.65041 11.0828 9.45835 10.8747C9.25828 10.6427 9.16225 10.3306 9.19426 10.0185ZM5.63312 9.89042L6.44138 6.4013H8.01788L7.55373 10.2905C7.48971 10.8107 7.07358 11.2028 6.58542 11.2028C6.1933 11.2028 5.94522 10.9708 5.84118 10.8267C5.62511 10.5706 5.55309 10.2265 5.63312 9.89042ZM6.40136 17.6049V12.7793C6.46538 12.7874 6.5214 12.8034 6.58542 12.8034C7.28165 12.8034 7.91385 12.5153 8.378 12.0431C8.85815 12.5233 9.49836 12.8034 10.2266 12.8034C10.9228 12.8034 11.547 12.5153 12.0112 12.0591C12.4833 12.5153 13.1235 12.8034 13.8438 12.8034C14.516 12.8034 15.1562 12.5233 15.6363 12.0431C16.1005 12.5153 16.7327 12.8034 17.4289 12.8034C17.4929 12.8034 17.549 12.7874 17.613 12.7793V17.6049H6.40136Z" fill="currentColor"/></symbol>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -410,12 +410,13 @@
} }
#mainContent > section { #mainContent > section {
padding: var(--btcpay-space-xl) var(--btcpay-space-l); padding: 4.65rem var(--btcpay-space-l);
} }
#mainContent > section > .container, #mainContent > section > .container,
.btcpay-footer > .container { .btcpay-footer > .container {
margin: 0; margin: 0;
max-width: min(1140px, calc(100vw - var(--sidebar-width) - (2 * var(--btcpay-space-l)) - 1rem)); /* 1rem for scrollbar */
} }
.btcpay-footer { .btcpay-footer {