mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +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 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.
|
||||
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).
|
||||
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.
|
||||
// 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 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; }
|
||||
public List<Field> Fields { get; set; } = new();
|
||||
|
||||
public virtual void Validate(ModelStateDictionary modelState)
|
||||
{
|
||||
if (Required && string.IsNullOrEmpty(Value))
|
||||
{
|
||||
modelState.AddModelError(Name, "This field is required");
|
||||
}
|
||||
}
|
||||
// The field is considered "valid" if there are no validation errors
|
||||
public List<string> ValidationErrors = new List<string>();
|
||||
|
||||
public bool IsValid()
|
||||
public virtual bool IsValid()
|
||||
{
|
||||
ModelStateDictionary modelState = new ModelStateDictionary();
|
||||
Validate(modelState);
|
||||
return modelState.IsValid;
|
||||
return ValidationErrors.Count == 0 && Fields.All(field => field.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?
|
||||
public bool IsValid()
|
||||
{
|
||||
return Validate(null);
|
||||
return Fields.Select(f => f.IsValid()).All(o => o);
|
||||
}
|
||||
|
||||
public Field GetFieldByName(string name)
|
||||
|
@ -65,16 +65,6 @@ public class Form
|
|||
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()
|
||||
{
|
||||
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.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
|
@ -41,6 +42,8 @@ namespace BTCPayServer.Controllers
|
|||
private readonly InvoiceRepository _InvoiceRepository;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
|
||||
public FormComponentProviders FormProviders { get; }
|
||||
|
||||
public UIPaymentRequestController(
|
||||
UIInvoiceController invoiceController,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
|
@ -49,7 +52,8 @@ namespace BTCPayServer.Controllers
|
|||
EventAggregator eventAggregator,
|
||||
CurrencyNameTable currencies,
|
||||
StoreRepository storeRepository,
|
||||
InvoiceRepository invoiceRepository)
|
||||
InvoiceRepository invoiceRepository,
|
||||
FormComponentProviders formProviders)
|
||||
{
|
||||
_InvoiceController = invoiceController;
|
||||
_UserManager = userManager;
|
||||
|
@ -59,6 +63,7 @@ namespace BTCPayServer.Controllers
|
|||
_Currencies = currencies;
|
||||
_storeRepository = storeRepository;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
FormProviders = formProviders;
|
||||
}
|
||||
|
||||
[BitpayAPIConstraint(false)]
|
||||
|
@ -204,7 +209,7 @@ namespace BTCPayServer.Controllers
|
|||
// POST case: Handle form submit
|
||||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(prFormId).Config);
|
||||
formData.ApplyValuesFromForm(this.Request.Form);
|
||||
if (formData.IsValid())
|
||||
if (FormProviders.Validate(formData, ModelState))
|
||||
{
|
||||
prBlob.FormResponse = JObject.FromObject(formData.GetValues());
|
||||
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)
|
||||
{
|
||||
serviceCollection.AddSingleton<FormDataService>();
|
||||
serviceCollection.AddSingleton<FormComponentProvider>();
|
||||
serviceCollection.AddSingleton<FormComponentProviders>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlInputFormProvider>();
|
||||
serviceCollection.AddSingleton<IFormComponentProvider, HtmlFieldsetFormProvider>();
|
||||
}
|
||||
|
|
|
@ -15,21 +15,21 @@ public class FormDataService
|
|||
|
||||
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()
|
||||
{
|
||||
Fields = new List<Field>()
|
||||
{
|
||||
new HtmlInputField("Enter your email", "buyerEmail", null, true, null, "email"),
|
||||
new HtmlInputField("Name", "buyerName", null, true, null),
|
||||
new HtmlInputField("Address Line 1", "buyerAddress1", null, true, null),
|
||||
new HtmlInputField("Address Line 2", "buyerAddress2", null, false, null),
|
||||
new HtmlInputField("City", "buyerCity", null, true, null),
|
||||
new HtmlInputField("Postcode", "buyerZip", null, false, null),
|
||||
new HtmlInputField("State", "buyerState", null, false, null),
|
||||
new HtmlInputField("Country", "buyerCountry", null, true, null)
|
||||
Field.Create("Enter your email", "buyerEmail", null, true, null, "email"),
|
||||
Field.Create("Name", "buyerName", null, true, null),
|
||||
Field.Create("Address Line 1", "buyerAddress1", null, true, null),
|
||||
Field.Create("Address Line 2", "buyerAddress2", null, false, null),
|
||||
Field.Create("City", "buyerCity", null, true, null),
|
||||
Field.Create("Postcode", "buyerZip", null, false, null),
|
||||
Field.Create("State", "buyerState", null, false, 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;
|
||||
|
||||
namespace BTCPayServer.Forms;
|
||||
|
||||
public class HtmlFieldsetFormProvider: IFormComponentProvider
|
||||
{
|
||||
public string CanHandle(Field field)
|
||||
public string View => "Forms/FieldSetElement";
|
||||
|
||||
public void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||
{
|
||||
return new[] { "fieldset"}.Contains(field.Type) ? "Forms/FieldSetElement" : null;
|
||||
typeToComponentProvider.Add("fieldset", this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Validate(Field field)
|
||||
{
|
||||
}
|
||||
|
||||
public void Validate(Form form, Field field)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Abstractions.Form;
|
||||
using BTCPayServer.Validation;
|
||||
|
||||
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",
|
||||
"radio",
|
||||
"checkbox",
|
||||
|
@ -29,6 +32,20 @@ public class HtmlInputFormProvider: IFormComponentProvider
|
|||
"search",
|
||||
"url",
|
||||
"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;
|
||||
|
||||
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)]
|
||||
public class UIFormsController : Controller
|
||||
{
|
||||
public FormComponentProviders FormProviders { get; }
|
||||
|
||||
public UIFormsController(FormComponentProviders formProviders)
|
||||
{
|
||||
FormProviders = formProviders;
|
||||
}
|
||||
[AllowAnonymous]
|
||||
[HttpGet("~/forms/{formId}")]
|
||||
[HttpPost("~/forms")]
|
||||
|
@ -39,24 +45,32 @@ public class UIFormsController : Controller
|
|||
: 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]
|
||||
[HttpPost("~/forms/{formId}")]
|
||||
public IActionResult SubmitForm(
|
||||
string formId,
|
||||
string? redirectUrl,
|
||||
string? command,
|
||||
[FromServices] StoreRepository storeRepository,
|
||||
[FromServices] UIInvoiceController invoiceController)
|
||||
{
|
||||
var formData = GetFormData(formId);
|
||||
if (formData?.Config is null)
|
||||
return NotFound();
|
||||
if (command is not "Submit")
|
||||
return GetFormView(formData, redirectUrl);
|
||||
|
||||
var conf = Form.Parse(formData.Config);
|
||||
conf.ApplyValuesFromForm(Request.Form);
|
||||
if (!conf.Validate(ModelState))
|
||||
return View("View", new FormViewModel() { FormData = formData, RedirectUrl = redirectUrl });
|
||||
if (!FormProviders.Validate(conf, ModelState))
|
||||
return GetFormView(formData, redirectUrl);
|
||||
|
||||
var form = new MultiValueDictionary<string, string>();
|
||||
foreach (var kv in Request.Form)
|
||||
|
|
|
@ -15,6 +15,7 @@ using BTCPayServer.Client;
|
|||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Forms;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
|
@ -40,19 +41,23 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
AppService appService,
|
||||
CurrencyNameTable currencies,
|
||||
StoreRepository storeRepository,
|
||||
UIInvoiceController invoiceController)
|
||||
UIInvoiceController invoiceController,
|
||||
FormComponentProviders formProviders)
|
||||
{
|
||||
_currencies = currencies;
|
||||
_appService = appService;
|
||||
_storeRepository = storeRepository;
|
||||
_invoiceController = invoiceController;
|
||||
FormProviders = formProviders;
|
||||
}
|
||||
|
||||
private readonly CurrencyNameTable _currencies;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly AppService _appService;
|
||||
private readonly UIInvoiceController _invoiceController;
|
||||
|
||||
|
||||
public FormComponentProviders FormProviders { get; }
|
||||
|
||||
[HttpGet("/")]
|
||||
[HttpGet("/apps/{appId}/pos/{viewType?}")]
|
||||
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
|
@ -232,7 +237,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
var formData = Form.Parse(Forms.UIFormsController.GetFormData(posFormId).Config);
|
||||
formData.ApplyValuesFromForm(this.Request.Form);
|
||||
|
||||
if (formData.IsValid())
|
||||
if (FormProviders.Validate(formData, ModelState))
|
||||
{
|
||||
formResponse = JObject.FromObject(formData.GetValues());
|
||||
break;
|
||||
|
|
|
@ -1,27 +1,19 @@
|
|||
@using BTCPayServer.Abstractions.Form
|
||||
@using BTCPayServer.Abstractions.Form
|
||||
@using BTCPayServer.Forms
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using Newtonsoft.Json.Linq
|
||||
@inject FormComponentProvider FormComponentProvider
|
||||
@inject FormComponentProviders FormComponentProviders
|
||||
@model BTCPayServer.Abstractions.Form.Field
|
||||
@{
|
||||
if (Model is not Fieldset fieldset)
|
||||
{
|
||||
fieldset = JObject.FromObject(Model).ToObject<Fieldset>();
|
||||
}
|
||||
}
|
||||
@if (!fieldset.Hidden)
|
||||
@if (!Model.Hidden)
|
||||
{
|
||||
<fieldset>
|
||||
<legend class="h3 mt-4 mb-3">@fieldset.Label</legend>
|
||||
@foreach (var field in fieldset.Fields)
|
||||
<legend class="h3 mt-4 mb-3">@Model.Label</legend>
|
||||
@foreach (var field in Model.Fields)
|
||||
{
|
||||
var partial = FormComponentProvider.CanHandle(field);
|
||||
if (string.IsNullOrEmpty(partial))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
<partial name="@partial" for="@field"></partial>
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||
{
|
||||
<partial name="@partial.View" for="@field"></partial>
|
||||
}
|
||||
}
|
||||
</fieldset>
|
||||
}
|
||||
|
|
|
@ -1,31 +1,34 @@
|
|||
@using BTCPayServer.Abstractions.Form
|
||||
@using BTCPayServer.Abstractions.Form
|
||||
@using Newtonsoft.Json.Linq
|
||||
@model BTCPayServer.Abstractions.Form.Field
|
||||
@{
|
||||
if (Model is not HtmlInputField field)
|
||||
{
|
||||
field = JObject.FromObject(Model).ToObject<HtmlInputField>();
|
||||
}
|
||||
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;
|
||||
|
||||
}
|
||||
<div class="form-group">
|
||||
@if (field.Required)
|
||||
@if (Model.Required)
|
||||
{
|
||||
<label class="form-label" for="@field.Name" data-required>
|
||||
@field.Label
|
||||
<label class="form-label" for="@Model.Name" data-required>
|
||||
@Model.Label
|
||||
</label>
|
||||
}
|
||||
else
|
||||
{
|
||||
<label class="form-label" for="@field.Name">
|
||||
@field.Label
|
||||
<label class="form-label" for="@Model.Name">
|
||||
@Model.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)"/>
|
||||
@if (!string.IsNullOrEmpty(field.HelpText))
|
||||
<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(isInvalid)
|
||||
{
|
||||
<span class="text-danger">@error</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||
{
|
||||
<small id="@("HelpText" + field.Name)" class="form-text text-muted">
|
||||
@field.HelpText
|
||||
<small id="@("HelpText" + Model.Name)" class="form-text text-muted">
|
||||
@Model.HelpText
|
||||
</small>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Forms
|
||||
@model BTCPayServer.Abstractions.Form.Form
|
||||
@inject FormComponentProvider FormComponentProvider
|
||||
@inject FormComponentProviders FormComponentProviders
|
||||
|
||||
@foreach (var field in Model.Fields)
|
||||
{
|
||||
var partial = FormComponentProvider.CanHandle(field);
|
||||
if (string.IsNullOrEmpty(partial))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
<partial name="@partial" for="@field"></partial>
|
||||
if (FormComponentProviders.TypeToComponentProvider.TryGetValue(field.Type, out var partial))
|
||||
{
|
||||
<partial name="@partial.View" for="@field"></partial>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<input type="hidden" asp-for="RedirectUrl" value="@Model.RedirectUrl"/>
|
||||
}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,16 +34,16 @@ public class FakeCustodian : ICustodian
|
|||
var fakeConfig = ParseConfig(config);
|
||||
|
||||
var form = new Form();
|
||||
var fieldset = new Fieldset();
|
||||
var fieldset = Field.CreateFieldset();
|
||||
|
||||
// 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.");
|
||||
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.");
|
||||
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.");
|
||||
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.");
|
||||
|
||||
fieldset.Label = "Your fake balances";
|
||||
|
|
Loading…
Add table
Reference in a new issue