using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PlotLine.Models; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] [Route("onboarding")] public sealed class OnboardingController(IOnboardingService onboarding) : Controller { [HttpGet("")] public async Task Index() { var model = await onboarding.GetWizardAsync(); return View(model); } [HttpGet("scan-review")] public async Task ScanReview(Guid? previewId) { var model = await onboarding.GetScanReviewAsync(previewId); return model is null ? NotFound() : View(model); } [HttpPost("welcome")] [ValidateAntiForgeryToken] public async Task Welcome() { await onboarding.MoveToNextStepAsync(); return RedirectToAction(nameof(Index)); } [HttpPost("writing-journey")] [ValidateAntiForgeryToken] public async Task WritingJourney(WritingJourneyOnboardingForm form) { if (!ModelState.IsValid || string.IsNullOrWhiteSpace(form.WritingJourney)) { var model = await onboarding.GetWizardAsync(); model.CurrentStep = OnboardingSteps.WritingJourney; model.StepNumber = 2; return View(nameof(Index), model); } await onboarding.SaveWritingJourneyAsync(form.WritingJourney); return RedirectToAction(nameof(Index)); } [HttpPost("writing-software")] [ValidateAntiForgeryToken] public async Task WritingSoftware(WritingSoftwareOnboardingForm form) { if (!ModelState.IsValid || string.IsNullOrWhiteSpace(form.WritingSoftware)) { var model = await onboarding.GetWizardAsync(); model.CurrentStep = OnboardingSteps.WritingSoftware; model.StepNumber = 3; return View(nameof(Index), model); } await onboarding.SaveWritingSoftwareAsync(form.WritingSoftware); return RedirectToAction(nameof(Index)); } [HttpPost("project")] [ValidateAntiForgeryToken] public async Task Project(ProjectOnboardingForm form) { if (string.Equals(form.Mode, OnboardingSelectionModes.Create, StringComparison.Ordinal) && string.IsNullOrWhiteSpace(form.ProjectName)) { ModelState.AddModelError(nameof(form.ProjectName), "Enter a project name."); } if (string.Equals(form.Mode, OnboardingSelectionModes.Select, StringComparison.Ordinal) && !form.SelectedProjectID.HasValue) { ModelState.AddModelError(nameof(form.SelectedProjectID), "Choose one of your active projects."); } if (!ModelState.IsValid) { var invalidModel = await onboarding.GetWizardAsync(); invalidModel.CurrentStep = OnboardingSteps.Project; invalidModel.StepNumber = 4; invalidModel.ProjectForm = form; return View(nameof(Index), invalidModel); } try { await onboarding.SaveProjectAsync(form); } catch (InvalidOperationException ex) { ModelState.AddModelError(string.Equals(form.Mode, OnboardingSelectionModes.Select, StringComparison.Ordinal) ? nameof(form.SelectedProjectID) : nameof(form.ProjectName), ex.Message); var invalidModel = await onboarding.GetWizardAsync(); invalidModel.CurrentStep = OnboardingSteps.Project; invalidModel.StepNumber = 4; invalidModel.ProjectForm = form; return View(nameof(Index), invalidModel); } return RedirectToAction(nameof(Index)); } [HttpPost("book")] [ValidateAntiForgeryToken] public async Task Book(BookOnboardingForm form) { if (string.Equals(form.Mode, OnboardingSelectionModes.Create, StringComparison.Ordinal) && string.IsNullOrWhiteSpace(form.BookTitle)) { ModelState.AddModelError(nameof(form.BookTitle), "Enter a book title."); } if (string.Equals(form.Mode, OnboardingSelectionModes.Select, StringComparison.Ordinal) && !form.SelectedBookID.HasValue) { ModelState.AddModelError(nameof(form.SelectedBookID), "Choose a book from the selected project."); } if (!ModelState.IsValid) { var invalidModel = await onboarding.GetWizardAsync(); invalidModel.CurrentStep = OnboardingSteps.Book; invalidModel.StepNumber = 5; invalidModel.BookForm = form; return View(nameof(Index), invalidModel); } try { await onboarding.SaveBookAsync(form); } catch (SubscriptionLimitException ex) { ModelState.AddModelError(nameof(form.BookTitle), ex.Message); var invalidModel = await onboarding.GetWizardAsync(); invalidModel.CurrentStep = OnboardingSteps.Book; invalidModel.StepNumber = 5; invalidModel.BookForm = form; return View(nameof(Index), invalidModel); } catch (InvalidOperationException ex) { ModelState.AddModelError(string.Equals(form.Mode, OnboardingSelectionModes.Select, StringComparison.Ordinal) ? nameof(form.SelectedBookID) : nameof(form.BookTitle), ex.Message); var invalidModel = await onboarding.GetWizardAsync(); invalidModel.CurrentStep = OnboardingSteps.Book; invalidModel.StepNumber = 5; invalidModel.BookForm = form; return View(nameof(Index), invalidModel); } return RedirectToAction(nameof(Index)); } [HttpPost("finish")] [ValidateAntiForgeryToken] public async Task Finish() { await onboarding.MarkCompletedAsync(); TempData["ArchiveMessage"] = "Setup saved. You can return to setup preferences from your account menu."; return RedirectToAction("Index", "Projects"); } [HttpPost("back")] [ValidateAntiForgeryToken] public async Task Back(string currentStep) { var targetStep = currentStep switch { OnboardingSteps.WritingJourney => OnboardingSteps.Welcome, OnboardingSteps.WritingSoftware => OnboardingSteps.WritingJourney, OnboardingSteps.Project => OnboardingSteps.WritingSoftware, OnboardingSteps.Book => OnboardingSteps.Project, OnboardingSteps.NextPathPreview => OnboardingSteps.Book, _ => OnboardingSteps.Welcome }; await onboarding.SetCurrentStepAsync(targetStep); return RedirectToAction(nameof(Index)); } }