diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/Register/RegisterDelegateByCentreControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/Register/RegisterDelegateByCentreControllerTests.cs index e1e7fcf4ad..752b4f1480 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/Register/RegisterDelegateByCentreControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/Register/RegisterDelegateByCentreControllerTests.cs @@ -22,6 +22,7 @@ public class RegisterDelegateByCentreControllerTests private IJobGroupsDataService jobGroupsDataService = null!; private IUserDataService userDataService = null!; private IUserService userService = null!; + private ICryptoService cryptoService = null!; [SetUp] public void Setup() @@ -30,10 +31,12 @@ public void Setup() userService = A.Fake(); userDataService = A.Fake(); customPromptHelper = A.Fake(); + cryptoService = A.Fake(); controller = new RegisterDelegateByCentreController( jobGroupsDataService, userService, customPromptHelper, + cryptoService, userDataService ) .WithDefaultContext() diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/ResetPasswordControllerTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/ResetPasswordControllerTests.cs index 9e38ced5d4..995f5e45e7 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/ResetPasswordControllerTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/ResetPasswordControllerTests.cs @@ -117,7 +117,7 @@ public async Task Post_to_index_should_invalidate_reset_hash_if_model_and_hash_v // When await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then A.CallTo(() => passwordResetService.InvalidateResetPasswordForEmailAsync("email")) @@ -133,7 +133,7 @@ public async Task Post_to_index_should_update_password_if_model_and_hash_valid() // When await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then A.CallTo(() => passwordService.ChangePasswordAsync("email", "testPass-9")) @@ -149,7 +149,7 @@ public async Task Post_to_index_should_return_success_page_if_model_and_hash_val // When var result = await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then result.Should().BeViewResult().WithViewName("Success"); @@ -165,7 +165,7 @@ public async Task Post_to_index_should_clear_temp_data_if_model_and_hash_valid() // When await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then unauthenticatedController.TempData.Peek().Should().BeNull(); @@ -182,7 +182,7 @@ public async Task Post_to_index_should_clear_temp_data_if_hash_invalid() // When await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then unauthenticatedController.TempData.Peek().Should().BeNull(); @@ -199,7 +199,7 @@ public async Task Post_to_index_should_preserve_temp_data_if_model_invalid() // When await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then unauthenticatedController.TempData.Peek().Should() @@ -216,7 +216,7 @@ public async Task Post_to_index_should_return_form_if_model_state_invalid() // When var result = await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then result.Should().BeViewResult().WithDefaultViewName(); @@ -231,7 +231,7 @@ public async Task Post_to_index_should_redirect_to_Error_if_reset_password_inval // When var result = await unauthenticatedController.Index( - new PasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); + new ConfirmPasswordViewModel { Password = "testPass-9", ConfirmPassword = "testPass-9" }); // Then result.Should().BeRedirectToActionResult().WithActionName("Error").WithControllerName(null); diff --git a/DigitalLearningSolutions.Web.Tests/Models/DelegateRegistrationByCentreDataTests.cs b/DigitalLearningSolutions.Web.Tests/Models/DelegateRegistrationByCentreDataTests.cs new file mode 100644 index 0000000000..2d5c772b58 --- /dev/null +++ b/DigitalLearningSolutions.Web.Tests/Models/DelegateRegistrationByCentreDataTests.cs @@ -0,0 +1,77 @@ +namespace DigitalLearningSolutions.Web.Tests.Models +{ + using System; + using DigitalLearningSolutions.Web.Models; + using DigitalLearningSolutions.Web.ViewModels.Register; + using DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre; + using FluentAssertions; + using NUnit.Framework; + + internal class DelegateRegistrationByCentreDataTests + { + private const string FirstName = "Test"; + private const string LastName = "User"; + private const string Email = "test@email.com"; + private const string Alias = "testuser"; + private const int CentreId = 5; + + [Test] + public void SetPersonalInformation_sets_data_correctly() + { + // Given + var model = new PersonalInformationViewModel + { + FirstName = FirstName, + LastName = LastName, + Centre = CentreId, + Email = Email, + Alias = Alias + }; + var data = new DelegateRegistrationByCentreData(); + + // When + data.SetPersonalInformation(model); + + // Then + data.FirstName.Should().Be(FirstName); + data.LastName.Should().Be(LastName); + data.Email.Should().Be(Email); + data.Centre.Should().Be(CentreId); + data.Alias.Should().Be(Alias); + } + + [Test] + public void SetWelcomeEmail_with_ShouldSendEmail_false_sets_data_correctly() + { + // Given + var model = new WelcomeEmailViewModel { ShouldSendEmail = false, Day = 7, Month = 7, Year = 2200 }; + var data = new DelegateRegistrationByCentreData(); + + // When + data.SetWelcomeEmail(model); + + // Then + data.ShouldSendEmail.Should().BeFalse(); + data.WelcomeEmailDate.Should().BeNull(); + } + + [Test] + public void SetWelcomeEmail_with_ShouldSendEmail_true_sets_data_correctly() + { + // Given + var date = new DateTime(2200, 7, 7); + var model = new WelcomeEmailViewModel + { ShouldSendEmail = true, Day = date.Day, Month = date.Month, Year = date.Year }; + var data = new DelegateRegistrationByCentreData(); + + // When + data.SetWelcomeEmail(model); + + // Then + data.ShouldSendEmail.Should().BeTrue(); + data.WelcomeEmailDate.Should().Be(date); + data.IsPasswordSet.Should().BeFalse(); + data.PasswordHash.Should().BeNull(); + } + } +} diff --git a/DigitalLearningSolutions.Web/ControllerHelpers/DateValidator.cs b/DigitalLearningSolutions.Web/ControllerHelpers/DateValidator.cs index 15a3bae30f..81c0df903a 100644 --- a/DigitalLearningSolutions.Web/ControllerHelpers/DateValidator.cs +++ b/DigitalLearningSolutions.Web/ControllerHelpers/DateValidator.cs @@ -39,6 +39,35 @@ public ValidationResult(int day, int month, int year) public int? Year { get; } } + public static ValidationResult ValidateRequiredDate(int? day, int? month, int? year, string name) + { + day ??= 0; + month ??= 0; + year ??= 0; + + try + { + var newDate = new DateTime(year.Value, month.Value, day.Value); + if (newDate <= DateTime.Today) + { + return new ValidationResult(day.Value, month.Value, year.Value) + { + DateValid = false, + DayValid = false, + MonthValid = false, + YearValid = false, + ErrorMessage = "Email delivery date must be in the future" + }; + } + + return new ValidationResult(day.Value, month.Value, year.Value); + } + catch (ArgumentOutOfRangeException) + { + return GetValidationError(day.Value, month.Value, year.Value, name); + } + } + public static ValidationResult ValidateDate(int day, int month, int year) { if (day == 0 && month == 0 && year == 0) @@ -70,11 +99,11 @@ public static ValidationResult ValidateDate(int day, int month, int year) } catch (ArgumentOutOfRangeException) { - return GetValidationError(day, month, year); + return GetValidationError(day, month, year, "Complete by date"); } } - private static ValidationResult GetValidationError(int day, int month, int year) + private static ValidationResult GetValidationError(int day, int month, int year, string name) { var error = new ValidationResult(day, month, year) { DateValid = false }; @@ -93,7 +122,7 @@ private static ValidationResult GetValidationError(int day, int month, int year) error.YearValid = false; } - error.ErrorMessage = ConstructErrorMessage(day, month, year); + error.ErrorMessage = ConstructErrorMessage(day, month, year, name); return error; } @@ -113,7 +142,7 @@ private static bool YearIsValid(int year) return year > 1752 && year < 10000; } - private static string ConstructErrorMessage(int day, int month, int year) + private static string ConstructErrorMessage(int day, int month, int year, string name) { List emptyElements = new List(); @@ -134,10 +163,10 @@ private static string ConstructErrorMessage(int day, int month, int year) if (emptyElements.Any()) { - return "Complete by date must include a " + string.Join(" and ", emptyElements); + return name + " must include a " + string.Join(" and ", emptyElements); } - return "Complete by date must be a real date"; + return name + " must be a real date"; } } } diff --git a/DigitalLearningSolutions.Web/Controllers/Register/RegisterAdminController.cs b/DigitalLearningSolutions.Web/Controllers/Register/RegisterAdminController.cs index 2c407e42f2..c5e580ac01 100644 --- a/DigitalLearningSolutions.Web/Controllers/Register/RegisterAdminController.cs +++ b/DigitalLearningSolutions.Web/Controllers/Register/RegisterAdminController.cs @@ -122,12 +122,12 @@ public IActionResult LearnerInformation(LearnerInformationViewModel model) [HttpGet] public IActionResult Password() { - return View(new PasswordViewModel()); + return View(new ConfirmPasswordViewModel()); } [ServiceFilter(typeof(RedirectEmptySessionData))] [HttpPost] - public IActionResult Password(PasswordViewModel model) + public IActionResult Password(ConfirmPasswordViewModel model) { if (!ModelState.IsValid) { diff --git a/DigitalLearningSolutions.Web/Controllers/Register/RegisterController.cs b/DigitalLearningSolutions.Web/Controllers/Register/RegisterController.cs index 612830b3c6..fccf5adf6e 100644 --- a/DigitalLearningSolutions.Web/Controllers/Register/RegisterController.cs +++ b/DigitalLearningSolutions.Web/Controllers/Register/RegisterController.cs @@ -160,12 +160,12 @@ public IActionResult LearnerInformation(LearnerInformationViewModel model) [HttpGet] public IActionResult Password() { - return View(new PasswordViewModel()); + return View(new ConfirmPasswordViewModel()); } [ServiceFilter(typeof(RedirectEmptySessionData))] [HttpPost] - public IActionResult Password(PasswordViewModel model) + public IActionResult Password(ConfirmPasswordViewModel model) { if (!ModelState.IsValid) { diff --git a/DigitalLearningSolutions.Web/Controllers/Register/RegisterDelegateByCentreController.cs b/DigitalLearningSolutions.Web/Controllers/Register/RegisterDelegateByCentreController.cs index 9fd3b1c593..712f2c33a3 100644 --- a/DigitalLearningSolutions.Web/Controllers/Register/RegisterDelegateByCentreController.cs +++ b/DigitalLearningSolutions.Web/Controllers/Register/RegisterDelegateByCentreController.cs @@ -5,16 +5,19 @@ namespace DigitalLearningSolutions.Web.Controllers.Register using System.Linq; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.Services; + using DigitalLearningSolutions.Web.ControllerHelpers; using DigitalLearningSolutions.Web.Extensions; using DigitalLearningSolutions.Web.Helpers; using DigitalLearningSolutions.Web.Models; using DigitalLearningSolutions.Web.ServiceFilter; using DigitalLearningSolutions.Web.ViewModels.Common; using DigitalLearningSolutions.Web.ViewModels.Register; + using DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.FeatureManagement.Mvc; + using SummaryViewModel = DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre.SummaryViewModel; [FeatureGate(FeatureFlags.RefactoredTrackingSystem)] [Authorize(Policy = CustomPolicies.UserCentreAdmin)] @@ -22,6 +25,7 @@ namespace DigitalLearningSolutions.Web.Controllers.Register public class RegisterDelegateByCentreController : Controller { private const string CookieName = "DelegateRegistrationByCentreData"; + private readonly ICryptoService cryptoService; private readonly CustomPromptHelper customPromptHelper; private readonly IJobGroupsDataService jobGroupsDataService; private readonly IUserDataService userDataService; @@ -31,6 +35,7 @@ public RegisterDelegateByCentreController( IJobGroupsDataService jobGroupsDataService, IUserService userService, CustomPromptHelper customPromptHelper, + ICryptoService cryptoService, IUserDataService userDataService ) { @@ -38,6 +43,7 @@ IUserDataService userDataService this.userService = userService; this.customPromptHelper = customPromptHelper; this.userDataService = userDataService; + this.cryptoService = cryptoService; } [Route("/TrackingSystem/Delegates/Register")] @@ -123,6 +129,80 @@ public IActionResult LearnerInformation(LearnerInformationViewModel model) data.SetLearnerInformation(model); TempData.Set(data); + return RedirectToAction("WelcomeEmail"); + } + + [ServiceFilter(typeof(RedirectEmptySessionData))] + [HttpGet] + public IActionResult WelcomeEmail() + { + var data = TempData.Peek()!; + + var model = new WelcomeEmailViewModel(data); + + return View(model); + } + + [ServiceFilter(typeof(RedirectEmptySessionData))] + [HttpPost] + public IActionResult WelcomeEmail(WelcomeEmailViewModel model) + { + var data = TempData.Peek()!; + + model.ClearDateIfNotSendEmail(); + SetWelcomeEmailValidationResult(model); + if (model.DateValidationResult is { DateValid: false }) + { + return View(model); + } + + data.SetWelcomeEmail(model); + TempData.Set(data); + + return RedirectToAction(data.ShouldSendEmail ? "Summary" : "Password"); + } + + [ServiceFilter(typeof(RedirectEmptySessionData))] + [HttpGet] + public IActionResult Password() + { + var model = new PasswordViewModel(); + + return View(model); + } + + [ServiceFilter(typeof(RedirectEmptySessionData))] + [HttpPost] + public IActionResult Password(PasswordViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + var data = TempData.Peek()!; + + data.PasswordHash = model.Password != null ? cryptoService.GetPasswordHash(model.Password) : null; + + TempData.Set(data); + + return RedirectToAction("Summary"); + } + + [ServiceFilter(typeof(RedirectEmptySessionData))] + [HttpGet] + public IActionResult Summary() + { + var data = TempData.Peek()!; + var viewModel = new SummaryViewModel(data); + PopulateSummaryExtraFields(viewModel, data); + return View(viewModel); + } + + [ServiceFilter(typeof(RedirectEmptySessionData))] + [HttpPost] + public IActionResult Summary(SummaryViewModel model) + { return new OkResult(); } @@ -178,6 +258,22 @@ private void ValidatePersonalInformation(PersonalInformationViewModel model) } } + private void SetWelcomeEmailValidationResult(WelcomeEmailViewModel model) + { + if (!model.ShouldSendEmail) + { + return; + } + + var validationResult = DateValidator.ValidateRequiredDate( + model.Day, + model.Month, + model.Year, + "Email delivery date" + ); + model.DateValidationResult = validationResult; + } + private IEnumerable GetEditCustomFieldsFromModel( LearnerInformationViewModel model, int centreId @@ -194,6 +290,19 @@ int centreId ); } + private IEnumerable GetCustomFieldsFromData(DelegateRegistrationData data) + { + return customPromptHelper.GetCustomFieldViewModelsForCentre( + data.Centre!.Value, + data.Answer1, + data.Answer2, + data.Answer3, + data.Answer4, + data.Answer5, + data.Answer6 + ); + } + private void PopulateLearnerInformationExtraFields( LearnerInformationViewModel model, RegistrationData data @@ -205,5 +314,11 @@ RegistrationData data model.JobGroup ); } + + private void PopulateSummaryExtraFields(SummaryViewModel model, DelegateRegistrationData data) + { + model.JobGroup = jobGroupsDataService.GetJobGroupName((int)data.JobGroup!); + model.CustomFields = GetCustomFieldsFromData(data); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/ResetPasswordController.cs b/DigitalLearningSolutions.Web/Controllers/ResetPasswordController.cs index a851a1bdaf..d7dc3a6acb 100644 --- a/DigitalLearningSolutions.Web/Controllers/ResetPasswordController.cs +++ b/DigitalLearningSolutions.Web/Controllers/ResetPasswordController.cs @@ -41,12 +41,12 @@ public async Task Index(string email, string code) return RedirectToAction("Error"); } - return View(new PasswordViewModel()); + return View(new ConfirmPasswordViewModel()); } [HttpPost] [ServiceFilter(typeof(RedirectEmptySessionData))] - public async Task Index(PasswordViewModel viewModel) + public async Task Index(ConfirmPasswordViewModel viewModel) { var resetPasswordData = TempData.Peek()!; diff --git a/DigitalLearningSolutions.Web/Models/DelegateRegistrationByCentreData.cs b/DigitalLearningSolutions.Web/Models/DelegateRegistrationByCentreData.cs index d0e0841322..e0085c468c 100644 --- a/DigitalLearningSolutions.Web/Models/DelegateRegistrationByCentreData.cs +++ b/DigitalLearningSolutions.Web/Models/DelegateRegistrationByCentreData.cs @@ -1,9 +1,37 @@ namespace DigitalLearningSolutions.Web.Models { + using System; + using DigitalLearningSolutions.Web.ViewModels.Register; + using DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre; + public class DelegateRegistrationByCentreData : DelegateRegistrationData { public DelegateRegistrationByCentreData() { } public DelegateRegistrationByCentreData(int centreId) : base(centreId) { } + public string? Alias { get; set; } + public DateTime? WelcomeEmailDate { get; set; } + + public bool ShouldSendEmail => WelcomeEmailDate.HasValue; + public bool IsPasswordSet => PasswordHash != null; + + public override void SetPersonalInformation(PersonalInformationViewModel model) + { + base.SetPersonalInformation(model); + Alias = model.Alias; + } + + public void SetWelcomeEmail(WelcomeEmailViewModel model) + { + if (model.ShouldSendEmail) + { + WelcomeEmailDate = new DateTime(model.Year!.Value, model.Month!.Value, model.Day!.Value); + PasswordHash = null; + } + else + { + WelcomeEmailDate = null; + } + } } } diff --git a/DigitalLearningSolutions.Web/Models/RegistrationData.cs b/DigitalLearningSolutions.Web/Models/RegistrationData.cs index 4359d8da4f..db6d36a18b 100644 --- a/DigitalLearningSolutions.Web/Models/RegistrationData.cs +++ b/DigitalLearningSolutions.Web/Models/RegistrationData.cs @@ -27,7 +27,7 @@ public RegistrationData(int? centreId) public string? PasswordHash { get; set; } - public void SetPersonalInformation(PersonalInformationViewModel model) + public virtual void SetPersonalInformation(PersonalInformationViewModel model) { Centre = model.Centre; Email = model.Email; diff --git a/DigitalLearningSolutions.Web/ViewComponents/DateInputViewComponent.cs b/DigitalLearningSolutions.Web/ViewComponents/DateInputViewComponent.cs new file mode 100644 index 0000000000..462492afdc --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewComponents/DateInputViewComponent.cs @@ -0,0 +1,54 @@ +namespace DigitalLearningSolutions.Web.ViewComponents +{ + using DigitalLearningSolutions.Web.ControllerHelpers; + using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents; + using Microsoft.AspNetCore.Mvc; + + public class DateInputViewComponent : ViewComponent + { + /// + /// Render DateInput view component. + /// + /// + /// + /// + /// + /// + /// DateValidator.ValidationResult + /// Leave blank for no hint. + /// + public IViewComponentResult Invoke( + string id, + string label, + string dayId, + string monthId, + string yearId, + DateValidator.ValidationResult validationResult, + string hintText + ) + { + var model = ViewData.Model; + + var dayProperty = model.GetType().GetProperty(dayId); + var monthProperty = model.GetType().GetProperty(monthId); + var yearProperty = model.GetType().GetProperty(yearId); + var dayValue = dayProperty?.GetValue(model)?.ToString(); + var monthValue = monthProperty?.GetValue(model)?.ToString(); + var yearValue = yearProperty?.GetValue(model)?.ToString(); + + var viewModel = new DateInputViewModel( + id, + label, + dayId, + monthId, + yearId, + dayValue, + monthValue, + yearValue, + validationResult, + string.IsNullOrEmpty(hintText) ? null : hintText + ); + return View(viewModel); + } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Common/ConfirmPasswordViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Common/ConfirmPasswordViewModel.cs new file mode 100644 index 0000000000..14d0953fd1 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Common/ConfirmPasswordViewModel.cs @@ -0,0 +1,22 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Common +{ + using System.ComponentModel.DataAnnotations; + + public class ConfirmPasswordViewModel + { + [Required(ErrorMessage = "Enter a password")] + [MinLength(8, ErrorMessage = "Password must be 8 characters or more")] + [MaxLength(100, ErrorMessage = "Password must be 100 characters or fewer")] + [RegularExpression( + @"(?=.*?[^\w\s])(?=.*?[0-9])(?=.*?[A-Za-z]).*", + ErrorMessage = "Password must contain at least 1 letter, 1 number and 1 symbol" + )] + [DataType(DataType.Password)] + public string? Password { get; set; } + + [Required(ErrorMessage = "Repeat your password to confirm")] + [DataType(DataType.Password)] + [Compare("Password", ErrorMessage = "Password and re-typed password must match")] + public string? ConfirmPassword { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Common/PasswordViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Common/PasswordViewModel.cs index 60829b7e0d..7a92a54634 100644 --- a/DigitalLearningSolutions.Web/ViewModels/Common/PasswordViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/Common/PasswordViewModel.cs @@ -4,7 +4,6 @@ public class PasswordViewModel { - [Required(ErrorMessage = "Enter a password")] [MinLength(8, ErrorMessage = "Password must be 8 characters or more")] [MaxLength(100, ErrorMessage = "Password must be 100 characters or fewer")] [RegularExpression( @@ -13,10 +12,5 @@ public class PasswordViewModel )] [DataType(DataType.Password)] public string? Password { get; set; } - - [Required(ErrorMessage = "Repeat your password to confirm")] - [DataType(DataType.Password)] - [Compare("Password", ErrorMessage = "Password and re-typed password must match")] - public string? ConfirmPassword { get; set; } } } diff --git a/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DateInputViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DateInputViewModel.cs new file mode 100644 index 0000000000..2e0ccde744 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Common/ViewComponents/DateInputViewModel.cs @@ -0,0 +1,51 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents +{ + using DigitalLearningSolutions.Web.ControllerHelpers; + + public class DateInputViewModel + { + public readonly bool HasDayError; + public readonly bool HasMonthError; + public readonly bool HasYearError; + + public DateInputViewModel( + string id, + string label, + string? dayId, + string? monthId, + string? yearId, + string? dayValue, + string? monthValue, + string? yearValue, + DateValidator.ValidationResult? validationResult, + string? hintText = null + ) + { + Id = id; + Label = label; + DayId = dayId ?? "Day"; + MonthId = monthId ?? "Month"; + YearId = yearId ?? "Year"; + DayValue = dayValue; + MonthValue = monthValue; + YearValue = yearValue; + HintText = hintText; + ErrorMessage = validationResult?.ErrorMessage; + HasDayError = validationResult is { DayValid: false }; + HasMonthError = validationResult is { MonthValid: false }; + HasYearError = validationResult is { YearValid: false }; + } + + public string Id { get; set; } + public string Label { get; set; } + public string DayId { get; set; } + public string MonthId { get; set; } + public string YearId { get; set; } + public string? DayValue { get; set; } + public string? MonthValue { get; set; } + public string? YearValue { get; set; } + public string? HintText { get; set; } + public bool HasError => HasDayError || HasMonthError || HasYearError; + public string? ErrorMessage { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/MyAccount/ChangePasswordViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/MyAccount/ChangePasswordViewModel.cs index 1cab9a96f5..8ebb75b64c 100644 --- a/DigitalLearningSolutions.Web/ViewModels/MyAccount/ChangePasswordViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/MyAccount/ChangePasswordViewModel.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using DigitalLearningSolutions.Web.ViewModels.Common; - public class ChangePasswordViewModel : PasswordViewModel + public class ChangePasswordViewModel : ConfirmPasswordViewModel { [Required(ErrorMessage = "Enter your password")] [DataType(DataType.Password)] diff --git a/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs new file mode 100644 index 0000000000..902cabff2e --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/SummaryViewModel.cs @@ -0,0 +1,35 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Web.Models; + using DigitalLearningSolutions.Web.ViewModels.Common; + + public class SummaryViewModel + { + public SummaryViewModel() { } + + public SummaryViewModel(DelegateRegistrationByCentreData data) + { + FirstName = data.FirstName; + LastName = data.LastName; + Email = data.Email; + Alias = data.Alias; + IsPasswordSet = data.IsPasswordSet; + if (data.ShouldSendEmail) + { + WelcomeEmailDate = data.WelcomeEmailDate!.Value.ToString("dd/MM/yyyy"); + } + } + + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Email { get; set; } + public string? Alias { get; set; } + public bool IsPasswordSet { get; set; } + public string? WelcomeEmailDate { get; set; } + public bool ShouldSendEmail => WelcomeEmailDate != null; + public string? JobGroup { get; set; } + public IEnumerable CustomFields { get; set; } = new List(); + public string PreviousAction => ShouldSendEmail ? "WelcomeEmail" : "Password"; + } +} diff --git a/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/WelcomeEmailViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/WelcomeEmailViewModel.cs new file mode 100644 index 0000000000..39c183a020 --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/Register/RegisterDelegateByCentre/WelcomeEmailViewModel.cs @@ -0,0 +1,37 @@ +namespace DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre +{ + using DigitalLearningSolutions.Web.ControllerHelpers; + using DigitalLearningSolutions.Web.Models; + + public class WelcomeEmailViewModel + { + public WelcomeEmailViewModel() { } + + public WelcomeEmailViewModel(DelegateRegistrationByCentreData data) + { + ShouldSendEmail = data.ShouldSendEmail; + if (ShouldSendEmail) + { + Day = data.WelcomeEmailDate!.Value.Day; + Month = data.WelcomeEmailDate!.Value.Month; + Year = data.WelcomeEmailDate!.Value.Year; + } + } + + public int? Day { get; set; } + public int? Month { get; set; } + public int? Year { get; set; } + public bool ShouldSendEmail { get; set; } + public DateValidator.ValidationResult? DateValidationResult { get; set; } + + public void ClearDateIfNotSendEmail() + { + if (!ShouldSendEmail) + { + Day = null; + Month = null; + Year = null; + } + } + } +} diff --git a/DigitalLearningSolutions.Web/Views/Register/Password.cshtml b/DigitalLearningSolutions.Web/Views/Register/Password.cshtml index 293a4a4f34..524bb3d2fa 100644 --- a/DigitalLearningSolutions.Web/Views/Register/Password.cshtml +++ b/DigitalLearningSolutions.Web/Views/Register/Password.cshtml @@ -1,5 +1,5 @@ @using DigitalLearningSolutions.Web.ViewModels.Common -@model PasswordViewModel +@model ConfirmPasswordViewModel @{ var errorHasOccurred = !ViewData.ModelState.IsValid; diff --git a/DigitalLearningSolutions.Web/Views/RegisterAdmin/Password.cshtml b/DigitalLearningSolutions.Web/Views/RegisterAdmin/Password.cshtml index b71d8c1d14..707973a0ad 100644 --- a/DigitalLearningSolutions.Web/Views/RegisterAdmin/Password.cshtml +++ b/DigitalLearningSolutions.Web/Views/RegisterAdmin/Password.cshtml @@ -1,5 +1,5 @@ @using DigitalLearningSolutions.Web.ViewModels.Common -@model PasswordViewModel +@model ConfirmPasswordViewModel @{ var errorHasOccurred = !ViewData.ModelState.IsValid; diff --git a/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/Password.cshtml b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/Password.cshtml new file mode 100644 index 0000000000..e7de259076 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/Password.cshtml @@ -0,0 +1,46 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.ViewModels.Common +@using Microsoft.Extensions.Configuration +@model PasswordViewModel + +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Register Delegate - Password" : "Register Delegate - Password"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; +} + +@section NavMenuItems { + +} + +
+
+ @if (errorHasOccurred) { + + } + +

Set password

+ +

+ You chose not to send a welcome email to the delegate. Would you like to set a password instead? +

+ +
+ + + + + + + +
+
diff --git a/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/PersonalInformation.cshtml b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/PersonalInformation.cshtml index 6bd762572e..3ac8bb9cb0 100644 --- a/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/PersonalInformation.cshtml +++ b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/PersonalInformation.cshtml @@ -16,7 +16,7 @@
-
+ @if (errorHasOccurred) { } diff --git a/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/Summary.cshtml b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/Summary.cshtml new file mode 100644 index 0000000000..e0b536e1f2 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/Summary.cshtml @@ -0,0 +1,139 @@ +@inject IConfiguration Configuration +@using DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre +@using Microsoft.Extensions.Configuration +@model SummaryViewModel +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = errorHasOccurred ? "Error: Register Delegate - Summary" : "Register Delegate - Summary"; + ViewData["Application"] = "Tracking System"; + ViewData["HeaderPath"] = $"{Configuration["AppRootPath"]}/TrackingSystem/Centre/Dashboard"; + ViewData["HeaderPathName"] = "Tracking System"; +} + +@section NavMenuItems { + +} + +
+
+
+

Summary

+
+ +
+
+
+ First name +
+
+ @Model.FirstName +
+
+ + Change first name + +
+
+
+
+ Last name +
+
+ @Model.LastName +
+
+ + Change last name + +
+
+
+
+ Email +
+
+ @Model.Email +
+
+ + Change email address + +
+
+
+
+ Alias (optional) +
+
+ @Model.Alias +
+
+ + Change alias + +
+
+
+
+ Job group +
+
+ @Model.JobGroup +
+
+ + Change job group + +
+
+ @foreach (var customField in Model.CustomFields) { +
+
+ @(customField.CustomPrompt + (customField.Mandatory ? "" : " (optional)")) +
+
+ @customField.Answer +
+
+ + Change @customField.CustomPrompt.ToLower() + +
+
+ } +
+
+ @(Model.ShouldSendEmail ? "Welcome email date" : "Send welcome email") +
+
+ @(Model.ShouldSendEmail ? Model.WelcomeEmailDate : "No") +
+
+ + Change welcome email settings + +
+
+ @if (!Model.ShouldSendEmail) { +
+
+ Password set +
+
+ @(Model.IsPasswordSet ? "Yes" : "No") +
+
+ + Change password settings + +
+
+ } +
+ + + + + +
+
diff --git a/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/WelcomeEmail.cshtml b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/WelcomeEmail.cshtml new file mode 100644 index 0000000000..00e5cbaf2c --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/RegisterDelegateByCentre/WelcomeEmail.cshtml @@ -0,0 +1,69 @@ +@using DigitalLearningSolutions.Web.ViewModels.Register.RegisterDelegateByCentre +@model WelcomeEmailViewModel + +@{ + var errorHasOccurred = Model.DateValidationResult is { DateValid: false }; + ViewData["Title"] = errorHasOccurred ? "Error: Register Delegate - Welcome Email" : "Register Delegate - Welcome Email"; + const string dateInputId = "welcome-email-date"; + const string conditionalDateInputId = "conditional-welcome-email-date"; + var exampleDate = DateTime.Today; + var emailDateCss = "nhsuk-checkboxes__conditional" + (Model.ShouldSendEmail ? "" : " nhsuk-checkboxes__conditional--hidden"); + var errorCss = errorHasOccurred ? "nhsuk-form-group--error nhsuk-u-padding-left-5" : ""; +} + +@section NavMenuItems { + +} + +
+
+ @if (errorHasOccurred) { + + } + +

Welcome email

+ +
+
+
+ + +
+ +
+ +
+
+ + +
+ + +
+
diff --git a/DigitalLearningSolutions.Web/Views/ResetPassword/Index.cshtml b/DigitalLearningSolutions.Web/Views/ResetPassword/Index.cshtml index 97367c8969..4f85d55a16 100644 --- a/DigitalLearningSolutions.Web/Views/ResetPassword/Index.cshtml +++ b/DigitalLearningSolutions.Web/Views/ResetPassword/Index.cshtml @@ -1,5 +1,5 @@ @using DigitalLearningSolutions.Web.ViewModels.Common -@model PasswordViewModel +@model ConfirmPasswordViewModel @{ var errorHasOccurred = !ViewData.ModelState.IsValid; diff --git a/DigitalLearningSolutions.Web/Views/Shared/Components/DateInput/Default.cshtml b/DigitalLearningSolutions.Web/Views/Shared/Components/DateInput/Default.cshtml new file mode 100644 index 0000000000..03f39c5a34 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/Shared/Components/DateInput/Default.cshtml @@ -0,0 +1,58 @@ +@using DigitalLearningSolutions.Web.ViewModels.Common.ViewComponents +@model DateInputViewModel + +@{ + var dayErrorCss = Model.HasDayError ? "nhsuk-input--error" : ""; + var monthErrorCss = Model.HasMonthError ? "nhsuk-input--error" : ""; + var yearErrorCss = Model.HasYearError ? "nhsuk-input--error" : ""; +} + +
+ + @Model.Label + + @if (Model.HintText != null) { +
+ @Model.HintText +
+ } + + @if (Model.HasError) { + + Error: @Model.ErrorMessage + + } + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+