From b7b2f16925abffdac7d64dac99067b7953e1c6ac Mon Sep 17 00:00:00 2001 From: dstrukt Date: Fri, 23 Jul 2021 03:57:19 -0700 Subject: [PATCH] Improves create point of sale view (#2646) * re-ordering * adds section header * updates label on "products" * changes button to primary * moves description * updates partial * re-ordering + section headers * more section heads and ordering * redorders * Toggle custom amount and tips settings * Use display name for point of sale app type * Use switches for enabling options * Add space before required indicator * Set and consolidate view model display names * Move redirects and custom CSS to additional options * Revert to checkbox for discounts * adds padding * removes bs-parent for multiple open elements on accordion * adds helper text to discount checkbox * updates "default view" label text * wording cleanup * more wording adjustments * updates * Add display names for app types * Extract template editor inline styles * updates helper text * Display names for app types * Typo fix * Move template back to editor * Fix selenium test Co-authored-by: Dennis Reimann --- BTCPayServer.Tests/SeleniumTests.cs | 4 +- BTCPayServer/Controllers/ServerController.cs | 2 +- BTCPayServer/Extensions/EnumExtensions.cs | 13 + .../AppViewModels/CreateAppViewModel.cs | 7 +- .../AppViewModels/UpdateCrowdfundViewModel.cs | 4 +- .../UpdatePointOfSaleViewModel.cs | 13 +- .../AppViewModels/ViewPointOfSaleViewModel.cs | 5 +- .../ListPaymentRequestsViewModel.cs | 2 +- .../WalletViewModels/PullPaymentsModel.cs | 2 +- BTCPayServer/Services/Apps/AppType.cs | 6 + BTCPayServer/Services/ThemesSettings.cs | 2 +- BTCPayServer/Views/Apps/ListApps.cshtml | 5 +- BTCPayServer/Views/Apps/TemplateEditor.cshtml | 31 +-- .../Views/Apps/UpdatePointOfSale.cshtml | 262 ++++++++++-------- BTCPayServer/bundleconfig.json | 6 +- .../wwwroot/main/bootstrap/bootstrap.css | 2 +- BTCPayServer/wwwroot/main/template-editor.css | 16 ++ 17 files changed, 230 insertions(+), 152 deletions(-) create mode 100644 BTCPayServer/Extensions/EnumExtensions.cs create mode 100644 BTCPayServer/wwwroot/main/template-editor.css diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index a1c1924cf..46adfe228 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -498,10 +498,9 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("Apps")).Click(); s.Driver.FindElement(By.Id("CreateNewApp")).Click(); s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid()); - s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale"); + s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Point of Sale"); s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName); s.Driver.FindElement(By.Id("Create")).Click(); - s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart"); s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click(); s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money"); s.Driver.FindElement(By.Id("SaveItemChanges")).Click(); @@ -510,6 +509,7 @@ namespace BTCPayServer.Tests var template = s.Driver.FindElement(By.Id("Template")).GetAttribute("value"); Assert.Contains("buyButtonText: Take my money", template); + s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Item list and cart"); s.Driver.FindElement(By.Id("SaveSettings")).Click(); s.Driver.FindElement(By.Id("ViewApp")).Click(); diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 96e44628c..43f4a7ede 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -408,7 +408,7 @@ namespace BTCPayServer.Controllers private async Task> GetAppSelectList() { var apps = (await _AppService.GetAllApps(null, true)) - .Select(a => new SelectListItem($"{a.AppType} - {a.AppName} - {a.StoreName}", a.Id)).ToList(); + .Select(a => new SelectListItem($"{typeof(AppType).DisplayName(a.AppType)} - {a.AppName} - {a.StoreName}", a.Id)).ToList(); apps.Insert(0, new SelectListItem("(None)", null)); return apps; } diff --git a/BTCPayServer/Extensions/EnumExtensions.cs b/BTCPayServer/Extensions/EnumExtensions.cs new file mode 100644 index 000000000..7dfc9e1c9 --- /dev/null +++ b/BTCPayServer/Extensions/EnumExtensions.cs @@ -0,0 +1,13 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; + +namespace BTCPayServer +{ + public static class EnumExtensions + { + public static string DisplayName(this Type enumType, string input) => + enumType.GetMember(input).First().GetCustomAttribute()?.Name ?? input; + } +} diff --git a/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs b/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs index 3c13b783d..f535d5d66 100644 --- a/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/CreateAppViewModel.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; using BTCPayServer.Data; using BTCPayServer.Services.Apps; using Microsoft.AspNetCore.Mvc.Rendering; @@ -44,7 +45,11 @@ namespace BTCPayServer.Models.AppViewModels void SetApps() { var defaultAppType = AppType.PointOfSale.ToString(); - var choices = typeof(AppType).GetEnumNames().Select(o => new Format() { Name = o, Value = o }).ToArray(); + var choices = typeof(AppType).GetEnumNames().Select(o => new Format + { + Name = typeof(AppType).DisplayName(o), + Value = o + }).ToArray(); var chosen = choices.FirstOrDefault(f => f.Value == defaultAppType) ?? choices.FirstOrDefault(); AppTypes = new SelectList(choices, nameof(chosen.Value), nameof(chosen.Name), chosen); SelectedAppType = chosen.Value; diff --git a/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs b/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs index 51cd8bbbc..8955af0f6 100644 --- a/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/UpdateCrowdfundViewModel.cs @@ -24,7 +24,7 @@ namespace BTCPayServer.Models.AppViewModels [Display(Name = "Featured Image")] public string MainImageUrl { get; set; } - [Display(Name = "Callback Notification Url")] + [Display(Name = "Callback Notification URL")] [Uri] public string NotificationUrl { get; set; } @@ -74,7 +74,7 @@ namespace BTCPayServer.Models.AppViewModels public string PerksTemplate { get; set; } [MaxLength(500)] - [Display(Name = "Custom bootstrap CSS file")] + [Display(Name = "Custom CSS URL")] public string CustomCSSLink { get; set; } [Display(Name = "Custom CSS Code")] diff --git a/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs b/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs index 5e9af8c04..5f72b9628 100644 --- a/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/UpdatePointOfSaleViewModel.cs @@ -19,7 +19,7 @@ namespace BTCPayServer.Models.AppViewModels public string Currency { get; set; } public string Template { get; set; } - [Display(Name = "Default view")] + [Display(Name = "Point of Sale Style")] public PosViewType DefaultView { get; set; } [Display(Name = "User can input custom amount")] public bool ShowCustomAmount { get; set; } @@ -32,20 +32,20 @@ namespace BTCPayServer.Models.AppViewModels public string ExampleCallback { get; internal set; } public string InvoiceUrl { get; internal set; } - [Display(Name = "Callback Notification Url")] + [Display(Name = "Callback Notification URL")] [Uri] public string NotificationUrl { get; set; } - [Display(Name = "Redirect Url")] + [Display(Name = "Redirect URL")] [Uri] public string RedirectUrl { get; set; } [Required] [MaxLength(30)] - [Display(Name = "Text to display on each buttons for items with a specific price")] + [Display(Name = "Text to display on each button for items with a specific price")] public string ButtonText { get; set; } [Required] [MaxLength(30)] - [Display(Name = "Text to display on buttons next to the input allowing the user to enter a custom amount")] + [Display(Name = "Text to display on buttons allowing the user to enter a custom amount")] public string CustomButtonText { get; set; } [Required] [MaxLength(30)] @@ -56,7 +56,7 @@ namespace BTCPayServer.Models.AppViewModels public string CustomTipPercentages { get; set; } [MaxLength(500)] - [Display(Name = "Custom bootstrap CSS file")] + [Display(Name = "Custom CSS URL")] public string CustomCSSLink { get; set; } public string Id { get; set; } @@ -87,6 +87,7 @@ namespace BTCPayServer.Models.AppViewModels } }, nameof(SelectListItem.Value), nameof(SelectListItem.Text), RedirectAutomatically); + [Display(Name = "Custom CSS Code")] public string EmbeddedCSS { get; set; } public string Description { get; set; } } diff --git a/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs b/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs index bbf450db6..7a0f6f7f5 100644 --- a/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs +++ b/BTCPayServer/Models/AppViewModels/ViewPointOfSaleViewModel.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using BTCPayServer.Services.Apps; namespace BTCPayServer.Models.AppViewModels @@ -49,9 +50,11 @@ namespace BTCPayServer.Models.AppViewModels public string CustomTipText { get; set; } public int[] CustomTipPercentages { get; set; } + [Display(Name = "Custom CSS URL")] public string CustomCSSLink { get; set; } public string CustomLogoLink { get; set; } public string Description { get; set; } + [Display(Name = "Custom CSS Code")] public string EmbeddedCSS { get; set; } } -} +} \ No newline at end of file diff --git a/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs b/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs index e8f3c6841..c3f189c0b 100644 --- a/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs +++ b/BTCPayServer/Models/PaymentRequestViewModels/ListPaymentRequestsViewModel.cs @@ -66,7 +66,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels public string Email { get; set; } [MaxLength(500)] - [Display(Name = "Custom bootstrap CSS file")] + [Display(Name = "Custom CSS URL")] public string CustomCSSLink { get; set; } [Display(Name = "Custom CSS Code")] diff --git a/BTCPayServer/Models/WalletViewModels/PullPaymentsModel.cs b/BTCPayServer/Models/WalletViewModels/PullPaymentsModel.cs index ea775e180..cb3c45d19 100644 --- a/BTCPayServer/Models/WalletViewModels/PullPaymentsModel.cs +++ b/BTCPayServer/Models/WalletViewModels/PullPaymentsModel.cs @@ -45,7 +45,7 @@ namespace BTCPayServer.Models.WalletViewModels [ReadOnly(true)] public string Currency { get; set; } [MaxLength(500)] - [Display(Name = "Custom bootstrap CSS file")] + [Display(Name = "Custom CSS URL")] public string CustomCSSLink { get; set; } [Display(Name = "Custom CSS Code")] public string EmbeddedCSS { get; set; } diff --git a/BTCPayServer/Services/Apps/AppType.cs b/BTCPayServer/Services/Apps/AppType.cs index 15d14e4d9..b3fa13d1a 100644 --- a/BTCPayServer/Services/Apps/AppType.cs +++ b/BTCPayServer/Services/Apps/AppType.cs @@ -1,15 +1,21 @@ +using System.ComponentModel.DataAnnotations; + namespace BTCPayServer.Services.Apps { public enum AppType { + [Display(Name = "Point of Sale")] PointOfSale, Crowdfund } public enum PosViewType { + [Display(Name = "Item list only")] Static, + [Display(Name = "Item list and cart")] Cart, + [Display(Name = "Keypad only")] Light } } diff --git a/BTCPayServer/Services/ThemesSettings.cs b/BTCPayServer/Services/ThemesSettings.cs index d1353051a..0bde669b9 100644 --- a/BTCPayServer/Services/ThemesSettings.cs +++ b/BTCPayServer/Services/ThemesSettings.cs @@ -17,7 +17,7 @@ namespace BTCPayServer.Services [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] [MaxLength(500)] - [Display(Name = "Custom bootstrap CSS file")] + [Display(Name = "Custom Bootstrap CSS file")] public string BootstrapCssUri { get; set; } [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] diff --git a/BTCPayServer/Views/Apps/ListApps.cshtml b/BTCPayServer/Views/Apps/ListApps.cshtml index 51432d3f6..cb984d1b0 100644 --- a/BTCPayServer/Views/Apps/ListApps.cshtml +++ b/BTCPayServer/Views/Apps/ListApps.cshtml @@ -1,4 +1,5 @@ -@model ListAppsViewModel +@using BTCPayServer.Services.Apps +@model ListAppsViewModel @{ ViewData.SetActivePageAndTitle(AppsNavPages.Index, "Apps"); var storeNameSortOrder = (string)ViewData["StoreNameSortOrder"]; @@ -85,7 +86,7 @@ } @app.AppName - @app.AppType + @typeof(AppType).DisplayName(app.AppType) @if (app.IsOwner) { diff --git a/BTCPayServer/Views/Apps/TemplateEditor.cshtml b/BTCPayServer/Views/Apps/TemplateEditor.cshtml index 2419c118f..f285eec22 100644 --- a/BTCPayServer/Views/Apps/TemplateEditor.cshtml +++ b/BTCPayServer/Views/Apps/TemplateEditor.cshtml @@ -1,30 +1,17 @@ @model (string templateId, string title) -
- +

@Model.title

@if (ViewContext.ViewData.ModelState.TryGetValue(Model.templateId, out var errors)) { foreach (var error in errors.Errors) { -
+
@error.ErrorMessage } } - +
@@ -38,14 +25,14 @@
- No items.
+ No items.
@@ -90,7 +77,7 @@
- +
@@ -102,11 +89,11 @@
- +
- +
diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index 2fb878100..23de9ed82 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -10,11 +10,12 @@

@ViewData["PageTitle"]

-
-
-
- -
+ + +
+ +
+
@@ -25,26 +26,30 @@
+
+
-
- - - -
-
- - - -
-
- - - -
-
- - - + + + +
+
+
+ +
+ + + +
+
+
+

Appearance

+
+ + + +
+ Choose the point of sale style for your customers.
@@ -52,89 +57,59 @@
-
- - - -
-
- - - -
-
- - - -
-
- - - - - - -
- - - -
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- -
- Invoices - -
-
- View App - -
-
- Back to the app list - +

Discounts

+
+ + + +
+ Not recommended for customer self-checkout.
+

Custom Payments

+
+ + + +
+
+
+ + + +
+
+

Tips

+
+ + + +
+
+
+ + + +
+
+ + + +
+
+
+
+

Additional Options

-
+

You can host point of sale buttons in an external website with the following code.

@if (Model.Example1 != null) @@ -153,13 +128,13 @@

-
+
- You can embed the POS using an iframe + You can embed this POS via an iframe. @{ var iframe = $""; } @@ -168,14 +143,41 @@
-

-

-
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ +

+
+
+
+ + + +

A POST callback will be sent to notification with the following form will be sent to notificationUrl once the enough is paid and once again once there is enough confirmations to the payment:

@Model.ExampleCallback

Never trust anything but id, ignore the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:

@@ -189,11 +191,53 @@
+
+

+ +

+
+
+
+ + + + + + +
+
+ + + +
+
+
+
- +
+ +
+ Invoices + +
+
+ View App + +
+ +
+
-
+
diff --git a/BTCPayServer/bundleconfig.json b/BTCPayServer/bundleconfig.json index 21da6732b..77860da7d 100644 --- a/BTCPayServer/bundleconfig.json +++ b/BTCPayServer/bundleconfig.json @@ -102,7 +102,8 @@ "outputFileName": "wwwroot/bundles/crowdfund-admin-bundle.min.css", "inputFiles": [ "wwwroot/vendor/highlightjs/default.min.css", - "wwwroot/vendor/summernote/summernote-bs5.css" + "wwwroot/vendor/summernote/summernote-bs5.css", + "wwwroot/main/template-editor.css" ] }, { @@ -120,7 +121,8 @@ "outputFileName": "wwwroot/bundles/pos-admin-bundle.min.css", "inputFiles": [ "wwwroot/vendor/highlightjs/default.min.css", - "wwwroot/vendor/summernote/summernote-bs5.css" + "wwwroot/vendor/summernote/summernote-bs5.css", + "wwwroot/main/template-editor.css" ] }, { diff --git a/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css b/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css index 2dbef719f..a222d7127 100644 --- a/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css +++ b/BTCPayServer/wwwroot/main/bootstrap/bootstrap.css @@ -9615,7 +9615,7 @@ input[type=number].hide-number-spin { } [data-required]::after { - content: "*"; + content: " *"; color: var(--btcpay-danger); } diff --git a/BTCPayServer/wwwroot/main/template-editor.css b/BTCPayServer/wwwroot/main/template-editor.css new file mode 100644 index 000000000..74f50001c --- /dev/null +++ b/BTCPayServer/wwwroot/main/template-editor.css @@ -0,0 +1,16 @@ +#template-editor-app .card-deck { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-gap: .5rem; +} + +#template-editor-app .card-deck .card{ + max-width: 480px; +} + +#template-editor-app .card-img-top { + min-height: 247px; + background-repeat: no-repeat; + background-size:contain; + background-position: top; +}