Skip to content

Commit 0aa291c

Browse files
committed
TD-6101: Login Wizard changes
1 parent dee48ff commit 0aa291c

File tree

8 files changed

+266
-113
lines changed

8 files changed

+266
-113
lines changed

LearningHub.Nhs.WebUI/Controllers/LoginWizardController.cs

Lines changed: 109 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -144,31 +144,7 @@ public async Task<IActionResult> Index(string returnUrl)
144144

145145
if (currentStage?.Id == (int)LoginWizardStageEnum.SecurityQuestions)
146146
{
147-
SecurityQuestionsViewModel securityQuestions = await this.loginWizardService.GetSecurityQuestionsModel(this.CurrentUserId);
148-
149-
while (securityQuestions.UserSecurityQuestions.Count < this.Settings.SecurityQuestionsToAsk)
150-
{
151-
securityQuestions.UserSecurityQuestions.Add(new UserSecurityQuestionViewModel());
152-
}
153-
154-
foreach (var answer in securityQuestions.UserSecurityQuestions)
155-
{
156-
if (!string.IsNullOrEmpty(answer.SecurityQuestionAnswerHash))
157-
{
158-
answer.SecurityQuestionAnswerHash = "********";
159-
}
160-
}
161-
162-
this.TempData.Clear();
163-
var securityViewModel = new SecurityViewModel()
164-
{
165-
SecurityQuestions = securityQuestions.SecurityQuestions,
166-
UserSecurityQuestions = securityQuestions.UserSecurityQuestions,
167-
};
168-
169-
await this.multiPageFormService.SetMultiPageFormData(securityViewModel, MultiPageFormDataFeature.EditRegistrationPrompt, this.TempData);
170-
171-
return this.RedirectToAction("SelectSecurityQuestion", new RouteValueDictionary { { "questionIndex", 0 }, { "returnUrl", returnUrl } });
147+
return this.RedirectToAction("SelectSecurityQuestions", new { returnUrl });
172148
}
173149

174150
if (currentStage?.Id == (int)LoginWizardStageEnum.JobRole || currentStage?.Id == (int)LoginWizardStageEnum.PlaceOfWork || currentStage?.Id == (int)LoginWizardStageEnum.PersonalDetails)
@@ -213,11 +189,19 @@ await this.multiPageFormService.SetMultiPageFormData(
213189
{
214190
return this.RedirectToAction("AccountInformationNeeded");
215191
}
192+
else if (currentStage?.Id == (int)LoginWizardStageEnum.JobRole || currentStage?.Id == (int)LoginWizardStageEnum.PlaceOfWork)
193+
{
194+
return this.RedirectToAction("MyEmploymentDetails", "MyAccount", new { returnUrl, checkDetails = true });
195+
}
216196
else
217197
{
218198
return this.RedirectToAction("Index", "MyAccount", new { returnUrl, checkDetails = true });
219199
}
220200
}
201+
else if (currentStage?.Id == (int)LoginWizardStageEnum.JobRole || currentStage?.Id == (int)LoginWizardStageEnum.PlaceOfWork)
202+
{
203+
return this.RedirectToAction("MyEmploymentDetails", "MyAccount", new { returnUrl, checkDetails = true });
204+
}
221205
else
222206
{
223207
return this.RedirectToAction("Index", "MyAccount", new { returnUrl, checkDetails = true });
@@ -585,6 +569,106 @@ public async Task<ActionResult> AccountConfirmationPost()
585569
return this.RedirectToAction("Index", new RouteValueDictionary { { "returnUrl", accountModel.WizardReturnUrl } });
586570
}
587571

572+
/// <summary>
573+
/// Action for starting the security question multiPageForm stage of the wizard.
574+
/// </summary>
575+
/// <param name="returnUrl">The URL to return to after the login wizard has been completed.</param>
576+
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
577+
[ResponseCache(CacheProfileName = "Never")]
578+
public async Task<IActionResult> SelectSecurityQuestions(string returnUrl)
579+
{
580+
MyAcountSecurityQuestionsViewModel securityViewModel = new MyAcountSecurityQuestionsViewModel();
581+
var result = await this.loginWizardService.GetSecurityQuestionsModel(this.CurrentUserId);
582+
583+
if (result != null)
584+
{
585+
securityViewModel.FirstSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedFirstQuestionId));
586+
securityViewModel.SecondSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedSecondQuestionId));
587+
}
588+
589+
this.ViewBag.ReturnUrl = returnUrl;
590+
return this.View("SecurityQuestionsDetails", securityViewModel);
591+
}
592+
593+
/// <summary>
594+
/// Action for choosing security questions.
595+
/// </summary>
596+
/// <param name="model">The MyAcountSecurityQuestionsViewModel.</param>
597+
/// <param name="returnUrl">The URL to return to after the login wizard has been completed.</param>
598+
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
599+
[HttpPost]
600+
[ResponseCache(CacheProfileName = "Never")]
601+
public async Task<IActionResult> UpdateSecurityQuestionPost(MyAcountSecurityQuestionsViewModel model, string returnUrl)
602+
{
603+
MyAcountSecurityQuestionsViewModel securityViewModel = new MyAcountSecurityQuestionsViewModel();
604+
var result = await this.loginWizardService.GetSecurityQuestionsModel(this.CurrentUserId);
605+
606+
if (result != null)
607+
{
608+
securityViewModel.FirstSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedFirstQuestionId));
609+
securityViewModel.SecondSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedSecondQuestionId));
610+
}
611+
612+
if (model != null)
613+
{
614+
if (model.SelectedFirstQuestionId == model.SelectedSecondQuestionId)
615+
{
616+
this.ModelState.AddModelError("DuplicateQuestion", CommonValidationErrorMessages.DuplicateQuestion);
617+
}
618+
619+
if (model.SelectedFirstQuestionId > 0 && string.IsNullOrEmpty(model.SecurityFirstQuestionAnswerHash))
620+
{
621+
this.ModelState.AddModelError(nameof(model.SecurityFirstQuestionAnswerHash), CommonValidationErrorMessages.InvalidSecurityQuestionAnswer);
622+
}
623+
624+
if (model.SelectedSecondQuestionId > 0 && string.IsNullOrEmpty(model.SecuritySecondQuestionAnswerHash))
625+
{
626+
this.ModelState.AddModelError(nameof(model.SecuritySecondQuestionAnswerHash), CommonValidationErrorMessages.InvalidSecurityQuestionAnswer);
627+
}
628+
629+
if (this.ModelState.IsValid)
630+
{
631+
var userSecurityQuestions = new List<UserSecurityQuestionViewModel>
632+
{
633+
new UserSecurityQuestionViewModel
634+
{
635+
SecurityQuestionId = model.SelectedFirstQuestionId,
636+
SecurityQuestionAnswerHash = model.SecurityFirstQuestionAnswerHash,
637+
UserId = this.CurrentUserId,
638+
},
639+
new UserSecurityQuestionViewModel
640+
{
641+
SecurityQuestionId = model.SelectedSecondQuestionId,
642+
SecurityQuestionAnswerHash = model.SecuritySecondQuestionAnswerHash,
643+
UserId = this.CurrentUserId,
644+
},
645+
};
646+
647+
await this.userService.UpdateUserSecurityQuestions(userSecurityQuestions);
648+
649+
// Mark stage complete.
650+
var (cacheExists, loginWizard) = await this.cacheService.TryGetAsync<LoginWizardViewModel>(this.LoginWizardCacheKey);
651+
652+
if (cacheExists)
653+
{
654+
await this.CompleteLoginWizardStageAsync(loginWizard, LoginWizardStageEnum.SecurityQuestions);
655+
this.TempData.Clear();
656+
return this.RedirectToAction("Index", new RouteValueDictionary { { "returnUrl", returnUrl } });
657+
}
658+
659+
this.TempData.Clear();
660+
return this.Redirect("/");
661+
}
662+
else
663+
{
664+
this.ViewBag.ReturnUrl = returnUrl;
665+
return this.View("SecurityQuestionsDetails", securityViewModel);
666+
}
667+
}
668+
669+
return this.View("SecurityQuestionsDetails", securityViewModel);
670+
}
671+
588672
/// <summary>
589673
/// The complete login wizard stage.
590674
/// </summary>

LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,35 @@ public async Task<IActionResult> Index(string returnUrl = null, bool? checkDetai
138138
/// <summary>
139139
/// MyEmploymentDetails.
140140
/// </summary>
141+
/// <param name="checkDetails">Whether to check account details.</param>
141142
/// <returns>IActionResult.</returns>
142143
[HttpGet]
143144
[Route("myaccount-employement")]
144-
public async Task<IActionResult> MyEmploymentDetails()
145+
public async Task<IActionResult> MyEmploymentDetails(bool? checkDetails = false)
145146
{
147+
string loginWizardCacheKey = $"{this.CurrentUserId}:LoginWizard";
148+
var (cacheExists, loginWizard) = await this.cacheService.TryGetAsync<Models.Account.LoginWizardViewModel>(loginWizardCacheKey);
149+
150+
if (checkDetails == true || cacheExists)
151+
{
152+
this.ViewBag.CheckDetails = true;
153+
154+
var rules = loginWizard.LoginWizardStagesRemaining.SelectMany(l => l.LoginWizardRules.Where(r => r.Required));
155+
foreach (var rule in rules)
156+
{
157+
this.ModelState.AddModelError(string.Empty, rule.Description);
158+
}
159+
160+
if (this.TempData.ContainsKey("IsJobRoleRequired"))
161+
{
162+
if (this.TempData["IsJobRoleRequired"] != null && (bool)this.TempData["IsJobRoleRequired"] == true)
163+
{
164+
this.ModelState.AddModelError(string.Empty, CommonValidationErrorMessages.RoleRequired);
165+
this.TempData["IsJobRoleRequired"] = null;
166+
}
167+
}
168+
}
169+
146170
var employmentDetails = await this.userService.GetMyEmploymentDetailsAsync();
147171
return this.View("MyEmployment", employmentDetails);
148172
}
@@ -312,13 +336,13 @@ public async Task<IActionResult> MyAccountSecurityQuestionsDetails([FromQuery] M
312336
return this.View("MyAccountSecurityQuestionsDetails", securityViewModel);
313337
}
314338

315-
if (viewModel.SelectedFirstQuestionId > 0)
339+
if (viewModel.SelectedFirstQuestionId > 0 && string.IsNullOrEmpty(viewModel.SecurityFirstQuestionAnswerHash))
316340
{
317341
this.ModelState.AddModelError(nameof(viewModel.SecurityFirstQuestionAnswerHash), CommonValidationErrorMessages.InvalidSecurityQuestionAnswer);
318342
return this.View("MyAccountSecurityQuestionsDetails", securityViewModel);
319343
}
320344

321-
if (viewModel.SelectedSecondQuestionId > 0)
345+
if (viewModel.SelectedSecondQuestionId > 0 && string.IsNullOrEmpty(viewModel.SecuritySecondQuestionAnswerHash))
322346
{
323347
this.ModelState.AddModelError(nameof(viewModel.SecuritySecondQuestionAnswerHash), CommonValidationErrorMessages.InvalidSecurityQuestionAnswer);
324348
return this.View("MyAccountSecurityQuestionsDetails", securityViewModel);
@@ -331,15 +355,15 @@ public async Task<IActionResult> MyAccountSecurityQuestionsDetails([FromQuery] M
331355
new UserSecurityQuestionViewModel
332356
{
333357
Id = securityViewModel.UserSecurityFirstQuestionId,
334-
SecurityQuestionId = securityViewModel.SelectedFirstQuestionId,
335-
SecurityQuestionAnswerHash = securityViewModel.SecurityFirstQuestionAnswerHash,
358+
SecurityQuestionId = viewModel.SelectedFirstQuestionId,
359+
SecurityQuestionAnswerHash = viewModel.SecurityFirstQuestionAnswerHash,
336360
UserId = this.CurrentUserId,
337361
},
338362
new UserSecurityQuestionViewModel
339363
{
340364
Id = securityViewModel.UserSecuritySecondQuestionId,
341-
SecurityQuestionId = securityViewModel.SelectedSecondQuestionId,
342-
SecurityQuestionAnswerHash = securityViewModel.SecuritySecondQuestionAnswerHash,
365+
SecurityQuestionId = viewModel.SelectedSecondQuestionId,
366+
SecurityQuestionAnswerHash = viewModel.SecuritySecondQuestionAnswerHash,
343367
UserId = this.CurrentUserId,
344368
},
345369
};

LearningHub.Nhs.WebUI/Styles/nhsuk/layout.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,10 @@ li.autosuggestion-option:last-of-type {
377377
transform: rotate(180deg);
378378
}
379379

380-
380+
.form-group-wrapper--error {
381+
border-left: none !important;
382+
padding-left: 0 !important;
383+
}
381384

382385
/* large desktop */
383386
@media (min-width: px2rem(990)) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@model LearningHub.Nhs.WebUI.Models.UserProfile.MyAcountSecurityQuestionsViewModel
2+
3+
@{
4+
ViewData["DisableValidation"] = true;
5+
ViewData["Title"] = "Login Wizard";
6+
var errorHasOccurred = !ViewData.ModelState.IsValid;
7+
}
8+
9+
<div class="bg-white">
10+
<div class="nhsuk-width-container app-width-container">
11+
@* <vc:back-link asp-controller="MyAccount" asp-action="Index" link-text="Go back" /> *@
12+
13+
<form asp-controller="LoginWizard" asp-action="UpdateSecurityQuestionPost" asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post">
14+
15+
<partial name="~/Views/MyAccount/_MyAccountSecurityQuestions.cshtml" model="@Model" />
16+
17+
<div class="nhsuk-u-padding-bottom-5">
18+
<input type="hidden" name="formSubmission" value="true">
19+
<button class="nhsuk-button" data-module="nhsuk-button" type="submit">
20+
Save changes
21+
</button>
22+
</div>
23+
</form>
24+
</div>
25+
</div>

LearningHub.Nhs.WebUI/Views/MyAccount/Index.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
{
133133
<form asp-controller="MyAccount" asp-action="CheckDetails" method="post">
134134
<input name="returnUrl" type="hidden" asp-for="@Context.Request.Query["returnUrl"]" />
135-
<div class="nhsuk-u-padding-bottom-5">
135+
<div class="nhsuk-u-padding-bottom-5 nhsuk-u-padding-top-5">
136136
<button class="nhsuk-button" data-module="nhsuk-button" type="submit">
137137
Confirm
138138
</button>

0 commit comments

Comments
 (0)