Apps become plugins

Unified navigation for apps that are now plugins. Part of #4744.
This commit is contained in:
Dennis Reimann
2023-03-18 22:36:26 +01:00
committed by d11n
parent d3f5576570
commit 7ad0aa82fc
10 changed files with 77 additions and 66 deletions

View File

@@ -77,8 +77,7 @@ namespace BTCPayServer.Tests
s.GenerateWallet(isHotWallet: true); s.GenerateWallet(isHotWallet: true);
// Point Of Sale // Point Of Sale
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click(); s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
new SelectElement(s.Driver.FindElement(By.Id("SelectedAppType"))).SelectByValue("PointOfSale");
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString()); s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
s.Driver.FindElement(By.Id("Create")).Click(); s.Driver.FindElement(By.Id("Create")).Click();
Assert.Contains("App successfully created", s.FindAlertMessage().Text); Assert.Contains("App successfully created", s.FindAlertMessage().Text);
@@ -939,9 +938,8 @@ namespace BTCPayServer.Tests
await s.StartAsync(); await s.StartAsync();
var userId = s.RegisterNewUser(true); var userId = s.RegisterNewUser(true);
s.CreateNewStore(); s.CreateNewStore();
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click(); s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
s.Driver.FindElement(By.Name("AppName")).SendKeys("PoS" + Guid.NewGuid()); s.Driver.FindElement(By.Name("AppName")).SendKeys("PoS" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Point of Sale");
s.Driver.FindElement(By.Id("Create")).Click(); s.Driver.FindElement(By.Id("Create")).Click();
Assert.Contains("App successfully created", s.FindAlertMessage().Text); Assert.Contains("App successfully created", s.FindAlertMessage().Text);
@@ -1027,9 +1025,8 @@ namespace BTCPayServer.Tests
s.CreateNewStore(); s.CreateNewStore();
s.AddDerivationScheme(); s.AddDerivationScheme();
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click(); s.Driver.FindElement(By.Id("StoreNav-CreateCrowdfund")).Click();
s.Driver.FindElement(By.Name("AppName")).SendKeys("CF" + Guid.NewGuid()); s.Driver.FindElement(By.Name("AppName")).SendKeys("CF" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
s.Driver.FindElement(By.Id("Create")).Click(); s.Driver.FindElement(By.Id("Create")).Click();
Assert.Contains("App successfully created", s.FindAlertMessage().Text); Assert.Contains("App successfully created", s.FindAlertMessage().Text);
@@ -2006,9 +2003,7 @@ namespace BTCPayServer.Tests
s.AddLightningNode(LightningConnectionType.CLightning, false); s.AddLightningNode(LightningConnectionType.CLightning, false);
s.GoToLightningSettings(); s.GoToLightningSettings();
s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true); s.Driver.SetCheckbox(By.Id("LNURLEnabled"), true);
s.Driver.FindElement(By.Id("StoreNav-CreateApp")).Click(); s.Driver.FindElement(By.Id("StoreNav-CreatePointOfSale")).Click();
s.Driver.FindElement(By.Id("SelectedAppType")).Click();
s.Driver.FindElement(By.CssSelector("option[value='PointOfSale']")).Click();
s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString()); s.Driver.FindElement(By.Id("AppName")).SendKeys(Guid.NewGuid().ToString());
s.Driver.FindElement(By.Id("Create")).Click(); s.Driver.FindElement(By.Id("Create")).Click();
TestUtils.Eventually(() => Assert.Contains("App successfully created", s.FindAlertMessage().Text)); TestUtils.Eventually(() => Assert.Contains("App successfully created", s.FindAlertMessage().Text));

View File

@@ -155,30 +155,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="accordion-item" permission="@Policies.CanModifyStoreSettings">
<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">
Apps
<vc:icon symbol="caret-down"/>
</button>
</header>
<div id="Nav-Apps" class="accordion-collapse collapse show" aria-labelledby="Nav-Apps-Header">
<div class="accordion-body">
<ul class="navbar-nav">
@foreach (var app in Model.Apps)
{
<vc:ui-extension-point location="apps-nav" model="@app"/>
}
<li class="nav-item">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Create)" id="StoreNav-CreateApp">
<vc:icon symbol="new"/>
<span>New App</span>
</a>
</li>
</ul>
</div>
</div>
</div>
} }
<div class="accordion-item"> <div class="accordion-item">
<header class="accordion-header" id="Nav-Plugins-Header"> <header class="accordion-header" id="Nav-Plugins-Header">
@@ -195,9 +171,11 @@
{ {
<vc:ui-extension-point location="store-integrations-nav" model="@Model"/> <vc:ui-extension-point location="store-integrations-nav" model="@Model"/>
} }
</ul>
<ul class="navbar-nav">
<li class="nav-item" permission="@Policies.CanModifyServerSettings"> <li class="nav-item" permission="@Policies.CanModifyServerSettings">
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-ManagePlugins"> <a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-ManagePlugins">
<vc:icon symbol="new"/> <vc:icon symbol="plugin"/>
<span>Manage Plugins</span> <span>Manage Plugins</span>
</a> </a>
</li> </li>

View File

@@ -111,22 +111,29 @@ namespace BTCPayServer.Controllers
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("/stores/{storeId}/apps/create")] [HttpGet("/stores/{storeId}/apps/create/{appType?}")]
public IActionResult CreateApp(string storeId, string appType = null) public IActionResult CreateApp(string storeId, string appType = null)
{ {
var vm = new CreateAppViewModel (_appService){StoreId = GetCurrentStore().Id, SelectedAppType = appType}; var vm = new CreateAppViewModel(_appService)
{
StoreId = storeId,
AppType = appType,
SelectedAppType = appType
};
return View(vm); return View(vm);
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("/stores/{storeId}/apps/create")] [HttpPost("/stores/{storeId}/apps/create/{appType?}")]
public async Task<IActionResult> CreateApp(string storeId, CreateAppViewModel vm) public async Task<IActionResult> CreateApp(string storeId, CreateAppViewModel vm)
{ {
var store = GetCurrentStore(); var store = GetCurrentStore();
vm.StoreId = store.Id; vm.StoreId = store.Id;
var type = _appService.GetAppType(vm.SelectedAppType); var type = _appService.GetAppType(vm.AppType ?? vm.SelectedAppType);
if (type is null) if (type is null)
{
ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type"); ModelState.AddModelError(nameof(vm.SelectedAppType), "Invalid App Type");
}
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
@@ -137,7 +144,7 @@ namespace BTCPayServer.Controllers
{ {
StoreDataId = store.Id, StoreDataId = store.Id,
Name = vm.AppName, Name = vm.AppName,
AppType = vm.SelectedAppType AppType = type!.Type
}; };
var defaultCurrency = await GetStoreDefaultCurrentIfEmpty(appData.StoreDataId, null); var defaultCurrency = await GetStoreDefaultCurrentIfEmpty(appData.StoreDataId, null);

View File

@@ -30,6 +30,7 @@ namespace BTCPayServer.Models.AppViewModels
public string SelectedAppType { get; set; } public string SelectedAppType { get; set; }
public SelectList AppTypes { get; set; } public SelectList AppTypes { get; set; }
public string AppType { get; set; }
private void SetApps(AppService appService) private void SetApps(AppService appService)
{ {

View File

@@ -30,7 +30,7 @@ namespace BTCPayServer.Plugins.Crowdfund
public override void Execute(IServiceCollection services) public override void Execute(IServiceCollection services)
{ {
services.AddSingleton<IUIExtension>(new UIExtension("Crowdfund/NavExtension", "apps-nav")); services.AddSingleton<IUIExtension>(new UIExtension("Crowdfund/NavExtension", "header-nav"));
services.AddSingleton<CrowdfundAppType>(); services.AddSingleton<CrowdfundAppType>();
services.AddSingleton<AppBaseType, CrowdfundAppType>(); services.AddSingleton<AppBaseType, CrowdfundAppType>();

View File

@@ -28,7 +28,7 @@ namespace BTCPayServer.Plugins.PointOfSale
public override void Execute(IServiceCollection services) public override void Execute(IServiceCollection services)
{ {
services.AddSingleton<IUIExtension>(new UIExtension("PointOfSale/NavExtension", "apps-nav")); services.AddSingleton<IUIExtension>(new UIExtension("PointOfSale/NavExtension", "header-nav"));
services.AddSingleton<AppBaseType, PointOfSaleAppType>(); services.AddSingleton<AppBaseType, PointOfSaleAppType>();
base.Execute(services); base.Execute(services);
} }

View File

@@ -4,16 +4,28 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.TagHelpers @using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.Crowdfund @using BTCPayServer.Plugins.Crowdfund
@model BTCPayServer.Components.MainNav.StoreApp @using BTCPayServer.Services.Apps
@inject AppService AppService;
@model BTCPayServer.Components.MainNav.MainNavViewModel
@{
var store = Context.GetStoreData();
}
@{ var store = Context.GetStoreData(); } @if (store != null)
@if (store != null && Model.AppType == CrowdfundAppType.AppType)
{ {
var appType = AppService.GetAppType(CrowdfundAppType.AppType)!;
<li class="nav-item" permission="@Policies.CanModifyStoreSettings"> <li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="UpdateCrowdfund" asp-route-appId="@Model.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Update, Model.Id)" id="@($"StoreNav-App-{Model.Id}")"> <a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type" class="nav-link @ViewData.IsActivePage(AppsNavPages.Create, appType.Type)" id="@($"StoreNav-Create{appType.Type}")">
<vc:icon symbol="@Model.AppType.ToLower()"/> <vc:icon symbol="crowdfund" />
<span>@Model.AppName</span> <span>@appType.Description</span>
</a> </a>
</li> </li>
@foreach (var app in Model.Apps.Where(app => app.AppType == appType.Type))
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="UpdateCrowdfund" asp-route-appId="@app.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<span>@app.AppName</span>
</a>
</li>
}
} }

View File

@@ -4,17 +4,28 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.TagHelpers @using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.PointOfSale @using BTCPayServer.Plugins.PointOfSale
@model BTCPayServer.Components.MainNav.StoreApp @using BTCPayServer.Services.Apps
@inject AppService AppService;
@{ var store = Context.GetStoreData(); } @model BTCPayServer.Components.MainNav.MainNavViewModel
@{
@if (store != null && Model.AppType == PointOfSaleAppType.AppType) var store = Context.GetStoreData();
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="UpdatePointOfSale" asp-route-appId="@Model.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Update, Model.Id)" id="@($"StoreNav-App-{Model.Id}")">
<vc:icon symbol="@Model.AppType.ToLower()"/>
<span>@Model.AppName</span>
</a>
</li>
} }
@if (store != null)
{
var appType = AppService.GetAppType(PointOfSaleAppType.AppType)!;
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type" class="nav-link @ViewData.IsActivePage(AppsNavPages.Create, appType.Type)" id="@($"StoreNav-Create{appType.Type}")">
<vc:icon symbol="pointofsale" />
<span>@appType.Description</span>
</a>
</li>
@foreach (var app in Model.Apps.Where(app => app.AppType == appType.Type))
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="UpdatePointOfSale" asp-route-appId="@app.Id" class="nav-link @ViewData.IsActivePage(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<span>@app.AppName</span>
</a>
</li>
}
}

View File

@@ -1,6 +1,6 @@
@model CreateAppViewModel @model CreateAppViewModel
@{ @{
ViewData.SetActivePage(AppsNavPages.Create, "Create a new app"); ViewData.SetActivePage(AppsNavPages.Create, $"Create a new {Model.AppType ?? "app"}", Model.AppType);
} }
@section PageFootContent { @section PageFootContent {
@@ -13,12 +13,15 @@
<div class="row"> <div class="row">
<div class="col-xl-8 col-xxl-constrain"> <div class="col-xl-8 col-xxl-constrain">
<form asp-action="CreateApp"> <form asp-action="CreateApp" asp-route-appType="@Model.AppType">
<div asp-validation-summary="ModelOnly" class="text-danger"></div> <div asp-validation-summary="ModelOnly" class="text-danger"></div>
@if (string.IsNullOrEmpty(Model.AppType))
{
<div class="form-group"> <div class="form-group">
<label asp-for="SelectedAppType" class="form-label" data-required></label> <label asp-for="SelectedAppType" class="form-label" data-required></label>
<select asp-for="SelectedAppType" asp-items="Model.AppTypes" class="form-select"></select> <select asp-for="SelectedAppType" asp-items="Model.AppTypes" class="form-select"></select>
</div> </div>
}
<div class="form-group"> <div class="form-group">
<label asp-for="AppName" class="form-label" data-required></label> <label asp-for="AppName" class="form-label" data-required></label>
<input asp-for="AppName" class="form-control" required /> <input asp-for="AppName" class="form-control" required />

View File

@@ -80,6 +80,10 @@
flex-shrink: 0; flex-shrink: 0;
} }
#mainNav .navbar-nav > li.nav-item-sub {
padding-left:calc(1.5rem + var(--btcpay-space-xs))
}
#mainNav .navbar-nav > li.nav-item .nav-link span { #mainNav .navbar-nav > li.nav-item .nav-link span {
max-width: 200px; max-width: 200px;
overflow: hidden; overflow: hidden;