diff --git a/Blocktrust.CredentialWorkflow.Core/Services/FormService.cs b/Blocktrust.CredentialWorkflow.Core/Services/FormService.cs index d4c99b0..bb32ddc 100644 --- a/Blocktrust.CredentialWorkflow.Core/Services/FormService.cs +++ b/Blocktrust.CredentialWorkflow.Core/Services/FormService.cs @@ -10,7 +10,7 @@ namespace Blocktrust.CredentialWorkflow.Core.Services; public interface IFormService { - Task> ProcessFormSubmission(Guid workflowId, Dictionary formData); + Task> ProcessFormSubmission(Guid workflowId, Dictionary formData); } public class FormService : IFormService @@ -26,7 +26,7 @@ public FormService(IMediator mediator, ILogger logger, IWorkflowQue _workflowQueue = workflowQueue; } - public async Task> ProcessFormSubmission(Guid workflowId, Dictionary formData) + public async Task> ProcessFormSubmission(Guid workflowId, Dictionary formData) { try { diff --git a/Blocktrust.CredentialWorkflow.Web/Components/Pages/DynamicForm.razor b/Blocktrust.CredentialWorkflow.Web/Components/Pages/DynamicForm.razor index 19c6afc..b07f4ac 100644 --- a/Blocktrust.CredentialWorkflow.Web/Components/Pages/DynamicForm.razor +++ b/Blocktrust.CredentialWorkflow.Web/Components/Pages/DynamicForm.razor @@ -9,193 +9,129 @@ @inject NavigationManager NavigationManager @inject ILogger Logger @inject IFormService FormService +@rendermode @(new InteractiveServerRenderMode(prerender: false)) + @attribute [AllowAnonymous] +@(formTitle ?? "Dynamic Form") +
- @if (isLoading) - { -
-
-
- } - else - { -
- @if (!string.IsNullOrEmpty(error)) - { -
-
-
-
- error -
-
-

Error

-
@error
-
-
-
-
- } - else if (!string.IsNullOrEmpty(successMessage)) - { -
-
-
- check_circle +
+ @if (isLoading) + { +
+
+
+ } + else if (!string.IsNullOrEmpty(error)) + { +
+
+
+
+ error
-

@successMessage

-
- +
+

Error

+
@error
- } - else - { -
-
-

@(formTitle ?? "New Form")

+
+ } + else if (isSuccess) + { +
+
+
+ check_circle +
+

Form submitted successfully!

+
+
+
+
+ } + else + { +
+
+

@formTitle

+
- -
- @foreach (var field in formModel.Fields) +
+ @foreach (var field in formFields) + { +
+ + + @if (field.AllowedValues?.Any() == true) { -
- - - @switch (field.Type.ToLower()) + + } + else + { + } -
- -
+ @if (!string.IsNullOrEmpty(field.ValidationMessage)) + { +

+ error + @field.ValidationMessage +

+ }
- + } + +
+ +
- } -
- } +
+ } +
@@ -204,56 +140,24 @@ private bool isLoading = true; private bool isSubmitting = false; + private bool isSuccess = false; private string? error; - private string? successMessage; private string? formTitle; - private DynamicFormModel formModel = new(); + private List formFields = new(); protected override async Task OnInitializedAsync() { try { - var result = await Mediator.Send(new GetWorkflowByIdRequest(WorkflowId)); - if (result.IsFailed) - { - error = "Unable to load the form. Please try again later."; - return; - } - - var workflow = result.Value; - if (workflow.ProcessFlow?.Triggers == null || !workflow.ProcessFlow.Triggers.Any()) - { - error = "This form is not properly configured."; - return; - } - - var trigger = workflow.ProcessFlow.Triggers.First().Value; - if (trigger.Type != ETriggerType.Form) - { - error = "Invalid form configuration."; - return; - } - - if (trigger.Input is TriggerInputForm formTrigger) + var result = await LoadWorkflowAndInitializeForm(); + if (!result.success) { - formTitle = workflow.Name; - foreach (var param in formTrigger.Parameters) - { - formModel.Fields.Add(new FormField - { - Name = param.Key, - Type = param.Value.Type.ToString().ToLower(), - Description = param.Value.Description, - IsRequired = param.Value.Required, - DefaultValue = param.Value.DefaultValue, - AllowedValues = param.Value.AllowedValues - }); - } + error = result.error; } } catch (Exception ex) { - error = "An error occurred while loading the form."; + error = "An unexpected error occurred while loading the form."; Logger.LogError(ex, "Error loading form for workflow {WorkflowId}", WorkflowId); } finally @@ -262,57 +166,67 @@ } } - private async Task HandleSubmit() + private async Task<(bool success, string? error)> LoadWorkflowAndInitializeForm() { - if (isSubmitting) return; + var workflowResult = await Mediator.Send(new GetWorkflowByIdRequest(WorkflowId)); + if (workflowResult.IsFailed) + { + return (false, "Unable to load the form. Please try again later."); + } - try + var workflow = workflowResult.Value; + if (workflow.ProcessFlow?.Triggers == null || !workflow.ProcessFlow.Triggers.Any()) { - // Clear any existing validation messages - foreach (var field in formModel.Fields) - { - field.ValidationMessage = null; - } + return (false, "This form is not properly configured."); + } - // Validate the form data - bool isValid = true; - foreach (var field in formModel.Fields.Where(f => f.IsRequired)) + var trigger = workflow.ProcessFlow.Triggers.First().Value; + if (trigger.Type != ETriggerType.Form) + { + return (false, "Invalid form configuration."); + } + + if (trigger.Input is not TriggerInputForm formTrigger) + { + return (false, "Invalid form trigger configuration."); + } + + formTitle = workflow.Name; + formFields.Clear(); + + foreach (var param in formTrigger.Parameters) + { + var field = new FormField { - switch (field.Type.ToLower()) - { - case "string": - case "email": - if (string.IsNullOrWhiteSpace(field.StringValue)) - { - field.ValidationMessage = "This field is required"; - isValid = false; - } - else if (field.Type.ToLower() == "email" && !IsValidEmail(field.StringValue)) - { - field.ValidationMessage = "Please enter a valid email address"; - isValid = false; - } - break; + Name = param.Key, + Type = param.Value.Type.ToString().ToLower(), + Description = param.Value.Description, + IsRequired = param.Value.Required, + DefaultValue = param.Value.DefaultValue, + AllowedValues = param.Value.AllowedValues, + Value = param.Value.DefaultValue ?? "" + }; + formFields.Add(field); + } - case "number": - if (!field.IsNumberSet) - { - field.ValidationMessage = "This field is required"; - isValid = false; - } - break; + return (true, null); + } - case "date": - if (field.DateValue == default) - { - field.ValidationMessage = "This field is required"; - isValid = false; - } - break; - } - } + private void UpdateFieldValue(FormField field, string? value) + { + field.Value = value ?? ""; + field.ValidationMessage = null; + } - if (!isValid) + private async Task HandleSubmit() + { + if (isSubmitting) return; + + try + { + ClearValidationMessages(); + + if (!ValidateForm()) { return; } @@ -320,24 +234,20 @@ isSubmitting = true; StateHasChanged(); - // Prepare form data - var formData = formModel.Fields.ToDictionary( + var formData = formFields.ToDictionary( field => field.Name, - field => field.GetValue() + field => field.Value ); - // Submit the form data var result = await FormService.ProcessFormSubmission(WorkflowId, formData); if (result.IsSuccess) { - successMessage = "Form submitted successfully!"; - StateHasChanged(); + isSuccess = true; } else { error = result.Errors.First().Message; - StateHasChanged(); } } catch (Exception ex) @@ -355,74 +265,39 @@ private bool ValidateForm() { bool isValid = true; - foreach (var field in formModel.Fields) + foreach (var field in formFields.Where(f => f.IsRequired)) { - if (field.IsRequired) + if (string.IsNullOrWhiteSpace(field.Value)) { - switch (field.Type.ToLower()) - { - case "string": - case "email": - if (string.IsNullOrWhiteSpace(field.StringValue)) - { - field.ValidationMessage = "This field is required"; - isValid = false; - } - else if (field.Type.ToLower() == "email" && !IsValidEmail(field.StringValue)) - { - field.ValidationMessage = "Please enter a valid email address"; - isValid = false; - } - break; - - case "number": - if (!field.IsNumberSet) - { - field.ValidationMessage = "This field is required"; - isValid = false; - } - break; - - case "date": - if (field.DateValue == default) - { - field.ValidationMessage = "This field is required"; - isValid = false; - } - break; - } + field.ValidationMessage = "This field is required"; + isValid = false; } } return isValid; } - private void ResetForm() + private void ClearValidationMessages() { - successMessage = null; - error = null; - formModel = new DynamicFormModel(); - OnInitializedAsync(); - } - - private bool IsValidEmail(string email) - { - try + foreach (var field in formFields) { - var addr = new System.Net.Mail.MailAddress(email); - return addr.Address == email; - } - catch - { - return false; + field.ValidationMessage = null; } } - public class DynamicFormModel + private async Task ResetForm() { - public List Fields { get; set; } = new(); + isSuccess = false; + error = null; + isLoading = true; + StateHasChanged(); + + await LoadWorkflowAndInitializeForm(); + + isLoading = false; + StateHasChanged(); } - public class FormField + private class FormField { public string Name { get; set; } = ""; public string Type { get; set; } = "string"; @@ -431,96 +306,6 @@ public string? DefaultValue { get; set; } public string[]? AllowedValues { get; set; } public string? ValidationMessage { get; set; } - - private string _stringValue = ""; - public string StringValue - { - get => _stringValue; - set - { - _stringValue = value ?? ""; - ValidationMessage = null; - } - } - - private decimal _numberValue; - private bool _isNumberSet; - public decimal NumberValue - { - get => _numberValue; - set - { - _numberValue = value; - _isNumberSet = true; - ValidationMessage = null; - } - } - public bool IsNumberSet => _isNumberSet; - - private bool _boolValue; - public bool BoolValue - { - get => _boolValue; - set - { - _boolValue = value; - ValidationMessage = null; - } - } - - private DateTime _dateValue = DateTime.Today; - public DateTime DateValue - { - get => _dateValue; - set - { - _dateValue = value; - ValidationMessage = null; - } - } - - public object GetValue() - { - return Type.ToLower() switch - { - "string" => StringValue, - "email" => StringValue, - "number" => NumberValue, - "boolean" => BoolValue, - "date" => DateValue.ToString("yyyy-MM-dd"), - _ => StringValue - }; - } - - public void SetDefaultValue() - { - if (string.IsNullOrEmpty(DefaultValue)) return; - - switch (Type.ToLower()) - { - case "string": - case "email": - StringValue = DefaultValue; - break; - case "number": - if (decimal.TryParse(DefaultValue, out decimal numValue)) - { - NumberValue = numValue; - } - break; - case "boolean": - if (bool.TryParse(DefaultValue, out bool boolValue)) - { - BoolValue = boolValue; - } - break; - case "date": - if (DateTime.TryParse(DefaultValue, out DateTime dateValue)) - { - DateValue = dateValue; - } - break; - } - } + public string Value { get; set; } = ""; } } \ No newline at end of file diff --git a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css index 4ac6d37..9d5f6a7 100644 --- a/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css +++ b/Blocktrust.CredentialWorkflow.Web/wwwroot/app.css @@ -1210,14 +1210,14 @@ video { border-width: 1px; } -.border-2 { - border-width: 2px; -} - .border-0 { border-width: 0px; } +.border-2 { + border-width: 2px; +} + .border-b { border-bottom-width: 1px; } @@ -1524,6 +1524,11 @@ video { padding-bottom: 0.5rem; } +.py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} + .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -1539,11 +1544,6 @@ video { padding-bottom: 2rem; } -.py-2\.5 { - padding-top: 0.625rem; - padding-bottom: 0.625rem; -} - .pb-5 { padding-bottom: 1.25rem; } @@ -1564,6 +1564,10 @@ video { padding-right: 2.5rem; } +.pr-3 { + padding-right: 0.75rem; +} + .pr-4 { padding-right: 1rem; } @@ -1576,10 +1580,6 @@ video { padding-top: 1.25rem; } -.pr-3 { - padding-right: 0.75rem; -} - .text-left { text-align: left; } @@ -1857,15 +1857,15 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.ring-2 { +.ring-1 { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } -.ring-1 { +.ring-2 { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } @@ -1873,14 +1873,14 @@ video { --tw-ring-inset: inset; } -.ring-slate-500 { +.ring-gray-300 { --tw-ring-opacity: 1; - --tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity)); + --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); } -.ring-gray-300 { +.ring-slate-500 { --tw-ring-opacity: 1; - --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); + --tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity)); } .transition { @@ -2104,11 +2104,6 @@ a { border-color: rgb(148 163 184 / var(--tw-border-opacity)); } -.focus\:border-slate-500:focus { - --tw-border-opacity: 1; - border-color: rgb(100 116 139 / var(--tw-border-opacity)); -} - .focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px;