mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +01:00
Propagate the ModelState errors on dynamic forms
This commit is contained in:
parent
5ff1a59a99
commit
31b25ca169
19 changed files with 206 additions and 155 deletions
|
@ -10,32 +10,55 @@ namespace BTCPayServer.Abstractions.Form;
|
||||||
|
|
||||||
public class Field
|
public class Field
|
||||||
{
|
{
|
||||||
|
public static Field Create(string label, string name, string value, bool required, string helpText, string type = "text")
|
||||||
|
{
|
||||||
|
return new Field()
|
||||||
|
{
|
||||||
|
Label = label,
|
||||||
|
Name = name,
|
||||||
|
Value = value,
|
||||||
|
OriginalValue = value,
|
||||||
|
Required = required,
|
||||||
|
HelpText = helpText,
|
||||||
|
Type = type
|
||||||
|
};
|
||||||
|
}
|
||||||
// The name of the HTML5 node. Should be used as the key for the posted data.
|
// The name of the HTML5 node. Should be used as the key for the posted data.
|
||||||
public string Name;
|
public string Name;
|
||||||
|
|
||||||
|
public bool Hidden;
|
||||||
|
|
||||||
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
|
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
|
||||||
public string Type;
|
public string Type;
|
||||||
|
|
||||||
|
public static Field CreateFieldset()
|
||||||
|
{
|
||||||
|
return new Field() { Type = "fieldset" };
|
||||||
|
}
|
||||||
|
|
||||||
// The value field is what is currently in the DB or what the user entered, but possibly not saved yet due to validation errors.
|
// The value field is what is currently in the DB or what the user entered, but possibly not saved yet due to validation errors.
|
||||||
// If this is the first the user sees the form, then value and original value are the same. Value changes as the user starts interacting with the form.
|
// If this is the first the user sees the form, then value and original value are the same. Value changes as the user starts interacting with the form.
|
||||||
public string Value;
|
public string Value;
|
||||||
|
|
||||||
public bool Required;
|
public bool Required;
|
||||||
|
|
||||||
|
// The translated label of the field.
|
||||||
|
public string Label;
|
||||||
|
|
||||||
|
// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
|
||||||
|
public string OriginalValue;
|
||||||
|
|
||||||
|
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
||||||
|
public string HelpText;
|
||||||
|
|
||||||
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
|
[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||||
public List<Field> Fields { get; set; } = new();
|
public List<Field> Fields { get; set; } = new();
|
||||||
|
|
||||||
public virtual void Validate(ModelStateDictionary modelState)
|
// The field is considered "valid" if there are no validation errors
|
||||||
{
|
public List<string> ValidationErrors = new List<string>();
|
||||||
if (Required && string.IsNullOrEmpty(Value))
|
|
||||||
{
|
|
||||||
modelState.AddModelError(Name, "This field is required");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsValid()
|
public virtual bool IsValid()
|
||||||
{
|
{
|
||||||
ModelStateDictionary modelState = new ModelStateDictionary();
|
return ValidationErrors.Count == 0 && Fields.All(field => field.IsValid());
|
||||||
Validate(modelState);
|
|
||||||
return modelState.IsValid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace BTCPayServer.Abstractions.Form;
|
|
||||||
|
|
||||||
public class Fieldset : Field
|
|
||||||
{
|
|
||||||
public bool Hidden { get; set; }
|
|
||||||
public string Label { get; set; }
|
|
||||||
|
|
||||||
public Fieldset()
|
|
||||||
{
|
|
||||||
Type = "fieldset";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,7 @@ public class Form
|
||||||
// Are all the fields valid in the form?
|
// Are all the fields valid in the form?
|
||||||
public bool IsValid()
|
public bool IsValid()
|
||||||
{
|
{
|
||||||
return Validate(null);
|
return Fields.Select(f => f.IsValid()).All(o => o);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Field GetFieldByName(string name)
|
public Field GetFieldByName(string name)
|
||||||
|
@ -65,16 +65,6 @@ public class Form
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
public bool Validate(ModelStateDictionary? modelState)
|
|
||||||
{
|
|
||||||
modelState ??= new ModelStateDictionary();
|
|
||||||
foreach (var field in Fields)
|
|
||||||
field.Validate(modelState);
|
|
||||||
return modelState.IsValid;
|
|
||||||
}
|
|
||||||
#nullable restore
|
|
||||||
|
|
||||||
public List<string> GetAllNames()
|
public List<string> GetAllNames()
|
||||||
{
|
{
|
||||||
return GetAllNames(Fields);
|
return GetAllNames(Fields);
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Abstractions.Form;
|
|
||||||
|
|
||||||
public class HtmlInputField : Field
|
|
||||||
{
|
|
||||||
// The translated label of the field.
|
|
||||||
public string Label;
|
|
||||||
|
|
||||||
// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
|
|
||||||
public string OriginalValue;
|
|
||||||
|
|
||||||
// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
|
|
||||||
public string HelpText;
|
|
||||||
|
|
||||||
public HtmlInputField(string label, string name, string value, bool required, string helpText, string type = "text")
|
|
||||||
{
|
|
||||||
Label = label;
|
|
||||||
Name = name;
|
|
||||||
Value = value;
|
|
||||||
OriginalValue = value;
|
|
||||||
Required = required;
|
|
||||||
HelpText = helpText;
|
|
||||||
Type = type;
|
|
||||||
}
|
|
||||||
// TODO JSON parsing from string to objects again probably won't work out of the box because of the different field types.
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Forms;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||||
using BTCPayServer.PaymentRequest;
|
using BTCPayServer.PaymentRequest;
|
||||||
|
@ -41,6 +42,8 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly InvoiceRepository _InvoiceRepository;
|
private readonly InvoiceRepository _InvoiceRepository;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
|
|
||||||
|
public FormComponentProviders FormProviders { get; }
|
||||||
|
|
||||||
public UIPaymentRequestController(
|
public UIPaymentRequestController(
|
||||||
UIInvoiceController invoiceController,
|
UIInvoiceController invoiceController,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
|
@ -49,7 +52,8 @@ namespace BTCPayServer.Controllers
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
InvoiceRepository invoiceRepository)
|
InvoiceRepository invoiceRepository,
|
||||||
|
FormComponentProviders formProviders)
|
||||||
{
|
{
|
||||||
_InvoiceController = invoiceController;
|
_InvoiceController = invoiceController;
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
|
@ -59,6 +63,7 @@ namespace BTCPayServer.Controllers
|
||||||
_Currencies = currencies;
|
_Currencies = currencies;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_InvoiceRepository = invoiceRepository;
|
_InvoiceRepository = invoiceRepository;
|
||||||
|
FormProviders = formProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
|
@ -204,7 +209,7 @@ namespace BTCPayServer.Controllers
|
||||||
// POST case: Handle form submit
|
// POST case: Handle form submit
|
||||||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(prFormId).Config);
|
var formData = Form.Parse(Forms.UIFormsController.GetFormData(prFormId).Config);
|
||||||
formData.ApplyValuesFromForm(this.Request.Form);
|
formData.ApplyValuesFromForm(this.Request.Form);
|
||||||
if (formData.IsValid())
|
if (FormProviders.Validate(formData, ModelState))
|
||||||
{
|
{
|
||||||
prBlob.FormResponse = JObject.FromObject(formData.GetValues());
|
prBlob.FormResponse = JObject.FromObject(formData.GetValues());
|
||||||
result.SetBlob(prBlob);
|
result.SetBlob(prBlob);
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using BTCPayServer.Abstractions.Form;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
|
||||||
|
|
||||||
public class FormComponentProvider : IFormComponentProvider
|
|
||||||
{
|
|
||||||
private readonly IEnumerable<IFormComponentProvider> _formComponentProviders;
|
|
||||||
|
|
||||||
public FormComponentProvider(IEnumerable<IFormComponentProvider> formComponentProviders)
|
|
||||||
{
|
|
||||||
_formComponentProviders = formComponentProviders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CanHandle(Field field)
|
|
||||||
{
|
|
||||||
return _formComponentProviders.Select(formComponentProvider => formComponentProvider.CanHandle(field)).FirstOrDefault(result => !string.IsNullOrEmpty(result));
|
|
||||||
}
|
|
||||||
}
|
|
34
BTCPayServer/Forms/FormComponentProviders.cs
Normal file
34
BTCPayServer/Forms/FormComponentProviders.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using BTCPayServer.Abstractions.Form;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Forms;
|
||||||
|
|
||||||
|
public class FormComponentProviders
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IFormComponentProvider> _formComponentProviders;
|
||||||
|
|
||||||
|
public Dictionary<string, IFormComponentProvider> TypeToComponentProvider = new Dictionary<string, IFormComponentProvider>();
|
||||||
|
|
||||||
|
public FormComponentProviders(IEnumerable<IFormComponentProvider> formComponentProviders)
|
||||||
|
{
|
||||||
|
_formComponentProviders = formComponentProviders;
|
||||||
|
foreach (var prov in _formComponentProviders)
|
||||||
|
prov.Register(TypeToComponentProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Validate(Form form, ModelStateDictionary modelState)
|
||||||
|
{
|
||||||
|
foreach (var field in form.Fields)
|
||||||
|
{
|
||||||
|
if (TypeToComponentProvider.TryGetValue(field.Type, out var provider))
|
||||||
|
{
|
||||||
|
provider.Validate(form, field);
|
||||||
|
foreach (var err in field.ValidationErrors)
|
||||||
|
modelState.TryAddModelError(field.Name, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modelState.IsValid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ public static class FormDataExtensions
|
||||||
public static void AddForms(this IServiceCollection serviceCollection)
|
public static void AddForms(this IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
serviceCollection.AddSingleton<FormDataService>();
|
serviceCollection.AddSingleton<FormDataService>();
|
||||||
serviceCollection.AddSingleton<FormComponentProvider>();
|
serviceCollection.AddSingleton<FormComponentProviders>();
|
||||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
||||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,21 @@ public class FormDataService
|
||||||
|
|
||||||
public static readonly Form StaticFormEmail = new()
|
public static readonly Form StaticFormEmail = new()
|
||||||
{
|
{
|
||||||
Fields = new List<Field>() {new HtmlInputField("Enter your email", "buyerEmail", null, true, null)}
|
Fields = new List<Field>() {Field.Create("Enter your email", "buyerEmail", null, true, null, "email")}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly Form StaticFormAddress = new()
|
public static readonly Form StaticFormAddress = new()
|
||||||
{
|
{
|
||||||
Fields = new List<Field>()
|
Fields = new List<Field>()
|
||||||
{
|
{
|
||||||
new HtmlInputField("Enter your email", "buyerEmail", null, true, null, "email"),
|
Field.Create("Enter your email", "buyerEmail", null, true, null, "email"),
|
||||||
new HtmlInputField("Name", "buyerName", null, true, null),
|
Field.Create("Name", "buyerName", null, true, null),
|
||||||
new HtmlInputField("Address Line 1", "buyerAddress1", null, true, null),
|
Field.Create("Address Line 1", "buyerAddress1", null, true, null),
|
||||||
new HtmlInputField("Address Line 2", "buyerAddress2", null, false, null),
|
Field.Create("Address Line 2", "buyerAddress2", null, false, null),
|
||||||
new HtmlInputField("City", "buyerCity", null, true, null),
|
Field.Create("City", "buyerCity", null, true, null),
|
||||||
new HtmlInputField("Postcode", "buyerZip", null, false, null),
|
Field.Create("Postcode", "buyerZip", null, false, null),
|
||||||
new HtmlInputField("State", "buyerState", null, false, null),
|
Field.Create("State", "buyerState", null, false, null),
|
||||||
new HtmlInputField("Country", "buyerCountry", null, true, null)
|
Field.Create("Country", "buyerCountry", null, true, null)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using BTCPayServer.Abstractions.Form;
|
using BTCPayServer.Abstractions.Form;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
namespace BTCPayServer.Forms;
|
||||||
|
|
||||||
public class HtmlFieldsetFormProvider: IFormComponentProvider
|
public class HtmlFieldsetFormProvider: IFormComponentProvider
|
||||||
{
|
{
|
||||||
public string CanHandle(Field field)
|
public string View => "Forms/FieldSetElement";
|
||||||
|
|
||||||
|
public void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||||
|
{
|
||||||
|
typeToComponentProvider.Add("fieldset", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Validate(Field field)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Validate(Form form, Field field)
|
||||||
{
|
{
|
||||||
return new[] { "fieldset"}.Contains(field.Type) ? "Forms/FieldSetElement" : null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
using BTCPayServer.Abstractions.Form;
|
using BTCPayServer.Abstractions.Form;
|
||||||
|
using BTCPayServer.Validation;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
namespace BTCPayServer.Forms;
|
||||||
|
|
||||||
public class HtmlInputFormProvider: IFormComponentProvider
|
public class HtmlInputFormProvider: FormComponentProviderBase
|
||||||
{
|
{
|
||||||
public string CanHandle(Field field)
|
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||||
{
|
{
|
||||||
return new[] {
|
foreach (var t in new[] {
|
||||||
"text",
|
"text",
|
||||||
"radio",
|
"radio",
|
||||||
"checkbox",
|
"checkbox",
|
||||||
|
@ -29,6 +32,20 @@ public class HtmlInputFormProvider: IFormComponentProvider
|
||||||
"search",
|
"search",
|
||||||
"url",
|
"url",
|
||||||
"tel",
|
"tel",
|
||||||
"reset"}.Contains(field.Type) ? "Forms/InputElement" : null;
|
"reset"})
|
||||||
|
typeToComponentProvider.Add(t, this);
|
||||||
|
}
|
||||||
|
public override string View => "Forms/InputElement";
|
||||||
|
|
||||||
|
public override void Validate(Form form, Field field)
|
||||||
|
{
|
||||||
|
if (field.Required)
|
||||||
|
{
|
||||||
|
ValidateField<RequiredAttribute>(field);
|
||||||
|
}
|
||||||
|
if (field.Type == "email")
|
||||||
|
{
|
||||||
|
ValidateField<MailboxAddressAttribute>(field);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,8 +1,26 @@
|
||||||
using BTCPayServer.Abstractions.Form;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using BTCPayServer.Abstractions.Form;
|
||||||
|
|
||||||
namespace BTCPayServer.Forms;
|
namespace BTCPayServer.Forms;
|
||||||
|
|
||||||
public interface IFormComponentProvider
|
public interface IFormComponentProvider
|
||||||
{
|
{
|
||||||
public string CanHandle(Field field);
|
string View { get; }
|
||||||
|
void Validate(Form form, Field field);
|
||||||
|
void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class FormComponentProviderBase : IFormComponentProvider
|
||||||
|
{
|
||||||
|
public abstract string View { get; }
|
||||||
|
public abstract void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider);
|
||||||
|
public abstract void Validate(Form form, Field field);
|
||||||
|
|
||||||
|
public void ValidateField<T>(Field field) where T : ValidationAttribute, new()
|
||||||
|
{
|
||||||
|
var result = new T().GetValidationResult(field.Value, new ValidationContext(field) { DisplayName = field.Label, MemberName = field.Name });
|
||||||
|
if (result != null)
|
||||||
|
field.ValidationErrors.Add(result.ErrorMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,12 @@ namespace BTCPayServer.Forms;
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
public class UIFormsController : Controller
|
public class UIFormsController : Controller
|
||||||
{
|
{
|
||||||
|
public FormComponentProviders FormProviders { get; }
|
||||||
|
|
||||||
|
public UIFormsController(FormComponentProviders formProviders)
|
||||||
|
{
|
||||||
|
FormProviders = formProviders;
|
||||||
|
}
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpGet("~/forms/{formId}")]
|
[HttpGet("~/forms/{formId}")]
|
||||||
[HttpPost("~/forms")]
|
[HttpPost("~/forms")]
|
||||||
|
@ -39,24 +45,32 @@ public class UIFormsController : Controller
|
||||||
: Redirect(redirectUrl);
|
: Redirect(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return View("View", new FormViewModel() { FormData = formData, RedirectUrl = redirectUrl });
|
return GetFormView(formData, redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ViewResult GetFormView(FormData formData, string? redirectUrl)
|
||||||
|
{
|
||||||
|
return View("View", new FormViewModel() { FormData = formData, RedirectUrl = redirectUrl });
|
||||||
|
}
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("~/forms/{formId}")]
|
[HttpPost("~/forms/{formId}")]
|
||||||
public IActionResult SubmitForm(
|
public IActionResult SubmitForm(
|
||||||
string formId,
|
string formId,
|
||||||
string? redirectUrl,
|
string? redirectUrl,
|
||||||
|
string? command,
|
||||||
[FromServices] StoreRepository storeRepository,
|
[FromServices] StoreRepository storeRepository,
|
||||||
[FromServices] UIInvoiceController invoiceController)
|
[FromServices] UIInvoiceController invoiceController)
|
||||||
{
|
{
|
||||||
var formData = GetFormData(formId);
|
var formData = GetFormData(formId);
|
||||||
if (formData?.Config is null)
|
if (formData?.Config is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
if (command is not "Submit")
|
||||||
|
return GetFormView(formData, redirectUrl);
|
||||||
|
|
||||||
var conf = Form.Parse(formData.Config);
|
var conf = Form.Parse(formData.Config);
|
||||||
conf.ApplyValuesFromForm(Request.Form);
|
conf.ApplyValuesFromForm(Request.Form);
|
||||||
if (!conf.Validate(ModelState))
|
if (!FormProviders.Validate(conf, ModelState))
|
||||||
return View("View", new FormViewModel() { FormData = formData, RedirectUrl = redirectUrl });
|
return GetFormView(formData, redirectUrl);
|
||||||
|
|
||||||
var form = new MultiValueDictionary<string, string>();
|
var form = new MultiValueDictionary<string, string>();
|
||||||
foreach (var kv in Request.Form)
|
foreach (var kv in Request.Form)
|
||||||
|
|
|
@ -15,6 +15,7 @@ using BTCPayServer.Client;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
|
using BTCPayServer.Forms;
|
||||||
using BTCPayServer.ModelBinders;
|
using BTCPayServer.ModelBinders;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||||
|
@ -40,12 +41,14 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
AppService appService,
|
AppService appService,
|
||||||
CurrencyNameTable currencies,
|
CurrencyNameTable currencies,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
UIInvoiceController invoiceController)
|
UIInvoiceController invoiceController,
|
||||||
|
FormComponentProviders formProviders)
|
||||||
{
|
{
|
||||||
_currencies = currencies;
|
_currencies = currencies;
|
||||||
_appService = appService;
|
_appService = appService;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_invoiceController = invoiceController;
|
_invoiceController = invoiceController;
|
||||||
|
FormProviders = formProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CurrencyNameTable _currencies;
|
private readonly CurrencyNameTable _currencies;
|
||||||
|
@ -53,6 +56,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
private readonly UIInvoiceController _invoiceController;
|
private readonly UIInvoiceController _invoiceController;
|
||||||
|
|
||||||
|
public FormComponentProviders FormProviders { get; }
|
||||||
|
|
||||||
[HttpGet("/")]
|
[HttpGet("/")]
|
||||||
[HttpGet("/apps/{appId}/pos/{viewType?}")]
|
[HttpGet("/apps/{appId}/pos/{viewType?}")]
|
||||||
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||||
|
@ -232,7 +237,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(posFormId).Config);
|
var formData = Form.Parse(Forms.UIFormsController.GetFormData(posFormId).Config);
|
||||||
formData.ApplyValuesFromForm(this.Request.Form);
|
formData.ApplyValuesFromForm(this.Request.Form);
|
||||||
|
|
||||||
if (formData.IsValid())
|
if (FormProviders.Validate(formData, ModelState))
|
||||||
{
|
{
|
||||||
formResponse = JObject.FromObject(formData.GetValues());
|
formResponse = JObject.FromObject(formData.GetValues());
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,27 +1,19 @@
|
||||||
@using BTCPayServer.Abstractions.Form
|
@using BTCPayServer.Abstractions.Form
|
||||||
@using BTCPayServer.Forms
|
@using BTCPayServer.Forms
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using Newtonsoft.Json.Linq
|
@using Newtonsoft.Json.Linq
|
||||||
@inject FormComponentProvider FormComponentProvider
|
@inject FormComponentProviders FormComponentProviders
|
||||||
@model BTCPayServer.Abstractions.Form.Field
|
@model BTCPayServer.Abstractions.Form.Field
|
||||||
@{
|
@if (!Model.Hidden)
|
||||||
if (Model is not Fieldset fieldset)
|
|
||||||
{
|
|
||||||
fieldset = JObject.FromObject(Model).ToObject<Fieldset>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if (!fieldset.Hidden)
|
|
||||||
{
|
{
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="h3 mt-4 mb-3">@fieldset.Label</legend>
|
<legend class="h3 mt-4 mb-3">@Model.Label</legend>
|
||||||
@foreach (var field in fieldset.Fields)
|
@foreach (var field in Model.Fields)
|
||||||
{
|
{
|
||||||
var partial = FormComponentProvider.CanHandle(field);
|
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||||
if (string.IsNullOrEmpty(partial))
|
|
||||||
{
|
{
|
||||||
continue;
|
<partial name="@partial.View" for="@field"></partial>
|
||||||
}
|
}
|
||||||
<partial name="@partial" for="@field"></partial>
|
|
||||||
}
|
}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,34 @@
|
||||||
@using BTCPayServer.Abstractions.Form
|
@using BTCPayServer.Abstractions.Form
|
||||||
@using Newtonsoft.Json.Linq
|
@using Newtonsoft.Json.Linq
|
||||||
@model BTCPayServer.Abstractions.Form.Field
|
@model BTCPayServer.Abstractions.Form.Field
|
||||||
@{
|
@{
|
||||||
if (Model is not HtmlInputField field)
|
var isInvalid = this.ViewContext.ModelState[Model.Name]?.ValidationState is Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid;
|
||||||
{
|
var error = isInvalid ? this.ViewContext.ModelState[Model.Name].Errors[0].ErrorMessage : null;
|
||||||
field = JObject.FromObject(Model).ToObject<HtmlInputField>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@if (field.Required)
|
@if (Model.Required)
|
||||||
{
|
{
|
||||||
<label class="form-label" for="@field.Name" data-required>
|
<label class="form-label" for="@Model.Name" data-required>
|
||||||
@field.Label
|
@Model.Label
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<label class="form-label" for="@field.Name">
|
<label class="form-label" for="@Model.Name">
|
||||||
@field.Label
|
@Model.Label
|
||||||
</label>
|
</label>
|
||||||
}
|
}
|
||||||
|
|
||||||
<input class="form-control @(field.IsValid() ? "" : "is-invalid")" id="@field.Name" type="@field.Type" required="@field.Required" name="@field.Name" value="@field.Value" aria-describedby="@("HelpText" + field.Name)"/>
|
<input class="form-control @(Model.IsValid() ? "" : "is-invalid")" id="@Model.Name" type="@Model.Type" required="@Model.Required" name="@Model.Name" value="@Model.Value" aria-describedby="@("HelpText" + Model.Name)"/>
|
||||||
@if (!string.IsNullOrEmpty(field.HelpText))
|
@if(isInvalid)
|
||||||
{
|
{
|
||||||
<small id="@("HelpText" + field.Name)" class="form-text text-muted">
|
<span class="text-danger">@error</span>
|
||||||
@field.HelpText
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||||
|
{
|
||||||
|
<small id="@("HelpText" + Model.Name)" class="form-text text-muted">
|
||||||
|
@Model.HelpText
|
||||||
</small>
|
</small>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@using BTCPayServer.Forms
|
@using BTCPayServer.Forms
|
||||||
@model BTCPayServer.Abstractions.Form.Form
|
@model BTCPayServer.Abstractions.Form.Form
|
||||||
@inject FormComponentProvider FormComponentProvider
|
@inject FormComponentProviders FormComponentProviders
|
||||||
|
|
||||||
@foreach (var field in Model.Fields)
|
@foreach (var field in Model.Fields)
|
||||||
{
|
{
|
||||||
var partial = FormComponentProvider.CanHandle(field);
|
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||||
if (string.IsNullOrEmpty(partial))
|
|
||||||
{
|
{
|
||||||
continue;
|
<partial name="@partial.View" for="@field"></partial>
|
||||||
}
|
}
|
||||||
<partial name="@partial" for="@field"></partial>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<input type="hidden" asp-for="RedirectUrl" value="@Model.RedirectUrl"/>
|
<input type="hidden" asp-for="RedirectUrl" value="@Model.RedirectUrl"/>
|
||||||
}
|
}
|
||||||
<partial name="_Form" model="@Model.Form"/>
|
<partial name="_Form" model="@Model.Form"/>
|
||||||
<input type="submit" class="btn btn-primary" value="Submit"/>
|
<input type="submit" class="btn btn-primary" name="command" value="Submit"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,16 +34,16 @@ public class FakeCustodian : ICustodian
|
||||||
var fakeConfig = ParseConfig(config);
|
var fakeConfig = ParseConfig(config);
|
||||||
|
|
||||||
var form = new Form();
|
var form = new Form();
|
||||||
var fieldset = new Fieldset();
|
var fieldset = Field.CreateFieldset();
|
||||||
|
|
||||||
// Maybe a decimal type field would be better?
|
// Maybe a decimal type field would be better?
|
||||||
var fakeBTCBalance = new HtmlInputField("BTC Balance", "BTCBalance", fakeConfig?.BTCBalance.ToString(), true,
|
var fakeBTCBalance = Field.Create("BTC Balance", "BTCBalance", fakeConfig?.BTCBalance.ToString(), true,
|
||||||
"Enter the amount of BTC you want to have.");
|
"Enter the amount of BTC you want to have.");
|
||||||
var fakeLTCBalance = new HtmlInputField("LTC Balance", "LTCBalance", fakeConfig?.LTCBalance.ToString(), true,
|
var fakeLTCBalance = Field.Create("LTC Balance", "LTCBalance", fakeConfig?.LTCBalance.ToString(), true,
|
||||||
"Enter the amount of LTC you want to have.");
|
"Enter the amount of LTC you want to have.");
|
||||||
var fakeEURBalance = new HtmlInputField("EUR Balance", "EURBalance", fakeConfig?.EURBalance.ToString(), true,
|
var fakeEURBalance = Field.Create("EUR Balance", "EURBalance", fakeConfig?.EURBalance.ToString(), true,
|
||||||
"Enter the amount of EUR you want to have.");
|
"Enter the amount of EUR you want to have.");
|
||||||
var fakeUSDBalance = new HtmlInputField("USD Balance", "USDBalance", fakeConfig?.USDBalance.ToString(), true,
|
var fakeUSDBalance = Field.Create("USD Balance", "USDBalance", fakeConfig?.USDBalance.ToString(), true,
|
||||||
"Enter the amount of USD you want to have.");
|
"Enter the amount of USD you want to have.");
|
||||||
|
|
||||||
fieldset.Label = "Your fake balances";
|
fieldset.Label = "Your fake balances";
|
||||||
|
|
Loading…
Add table
Reference in a new issue