PlotDirector/PlotLine/Controllers/OnboardingController.cs

290 lines
10 KiB
C#

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, IStoryIntelligenceService storyIntelligence) : Controller
{
[HttpGet("")]
public async Task<IActionResult> Index()
{
var model = await onboarding.GetWizardAsync();
return View(model);
}
[HttpGet("scan-review")]
public async Task<IActionResult> ScanReview(Guid? previewId)
{
var model = await onboarding.GetScanReviewAsync(previewId);
return model is null ? NotFound() : View(model);
}
[HttpGet("build-complete")]
public async Task<IActionResult> BuildComplete(Guid previewId)
{
var model = await onboarding.GetBuildResultAsync(previewId);
return model is null ? NotFound() : View(model);
}
[HttpGet("story-intelligence")]
public async Task<IActionResult> StoryIntelligence()
{
var model = await storyIntelligence.GetOverviewAsync();
return View(model);
}
[HttpPost("story-intelligence/start")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> StartStoryIntelligence()
{
var job = await storyIntelligence.StartAsync();
if (job is null)
{
TempData["ArchiveError"] = "Choose a project and book before starting Story Intelligence.";
return RedirectToAction(nameof(Index));
}
return RedirectToAction(nameof(StoryIntelligenceProgress), new { jobId = job.JobID });
}
[HttpPost("story-intelligence/skip")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SkipStoryIntelligence()
{
await onboarding.MarkCompletedAsync();
TempData["ArchiveMessage"] = "Story Intelligence skipped for now. You can return to it from your dashboard.";
return RedirectToAction("Index", "Projects");
}
[HttpGet("story-intelligence/progress")]
public async Task<IActionResult> StoryIntelligenceProgress(Guid jobId)
{
var model = await storyIntelligence.GetProgressAsync(jobId);
return model is null ? NotFound() : View(model);
}
[HttpPost("story-intelligence/cancel")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CancelStoryIntelligence(Guid jobId)
{
var job = await storyIntelligence.CancelAsync(jobId);
if (job is null)
{
return NotFound();
}
TempData["ArchiveMessage"] = "Story Intelligence setup was cancelled.";
return RedirectToAction("Index", "Projects");
}
[HttpGet("story-intelligence/complete")]
public async Task<IActionResult> StoryIntelligenceComplete(Guid jobId)
{
var model = await storyIntelligence.GetCompletionAsync(jobId);
return model is null ? NotFound() : View(model);
}
[HttpPost("scan-review")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SaveScanReview(ManuscriptScanReviewForm form, string intent = "save")
{
var readyToImport = string.Equals(intent, "continue", StringComparison.OrdinalIgnoreCase);
try
{
var model = await onboarding.SaveScanReviewAsync(form, readyToImport);
if (model is null)
{
return NotFound();
}
TempData["OnboardingReviewMessage"] = readyToImport
? "Review saved. Next: import into PlotDirector."
: "Review choices saved.";
return readyToImport
? RedirectToAction(nameof(Index))
: RedirectToAction(nameof(ScanReview), new { previewId = form.PreviewID });
}
catch (InvalidOperationException ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
var model = await onboarding.GetScanReviewAsync(form.PreviewID);
if (model is null)
{
return NotFound();
}
return View("ScanReview", model);
}
}
[HttpPost("welcome")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Welcome()
{
await onboarding.MoveToNextStepAsync();
return RedirectToAction(nameof(Index));
}
[HttpPost("writing-journey")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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));
}
}