Fix simulate route crash and surface actual error messages
When a simulation action threw (e.g. readiness check failure), React Router tried to render the simulate route via GET to display the error — but the route had no loader, so the real error was completely swallowed by React Router's own 400 "no loader" message. Four issues fixed: - Move simulate into the intent-dispatch pattern on the season page (matching delete/rescore/sync-standings/finalize-standings), so errors surface via actionData instead of being lost - Eliminate the ?simulationError= query param approach, which left stale error messages in browser history and reflected user-controlled text in the UI - Match the existing bg-destructive/15 error styling used elsewhere on the page - Strip the now-unused action from simulate.tsx; keep a loader-only redirect stub so any bookmarked URLs degrade gracefully to the season page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ac22ff3a12
commit
ba970cd571
2 changed files with 24 additions and 17 deletions
|
|
@ -1,24 +1,13 @@
|
|||
/**
|
||||
* Admin: Run EV Simulation for a Sports Season
|
||||
* Admin: Simulate action endpoint (stub)
|
||||
*
|
||||
* Action-only route. POSTing here:
|
||||
* 1. Delegates validation + execution to the shared simulator runner
|
||||
* 6. Redirects back to the sports season admin page
|
||||
* Simulation is now handled by intent="simulate" on the season admin page.
|
||||
* This stub redirects any direct GET navigation to that page.
|
||||
*/
|
||||
|
||||
import { redirect } from "react-router";
|
||||
import type { Route } from "./+types/admin.sports-seasons.$id.simulate";
|
||||
import { runSportsSeasonSimulation } from "~/services/simulations/runner";
|
||||
|
||||
export async function action({ params }: Route.ActionArgs) {
|
||||
const sportsSeasonId = params.id;
|
||||
try {
|
||||
await runSportsSeasonSimulation(sportsSeasonId);
|
||||
} catch (error) {
|
||||
throw new Response(error instanceof Error ? error.message : "Simulation failed", {
|
||||
status: error instanceof Error && error.message.includes("already running") ? 409 : 400,
|
||||
});
|
||||
}
|
||||
|
||||
return redirect(`/admin/sports-seasons/${sportsSeasonId}/expected-values`);
|
||||
export async function loader({ params }: Route.LoaderArgs) {
|
||||
return redirect(`/admin/sports-seasons/${params.id}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { database } from "~/database/context";
|
|||
import { participantEvSnapshots, seasonSports } from "~/database/schema";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { getSimulatorInfo, type SimulatorType } from "~/services/simulations/registry";
|
||||
import { runSportsSeasonSimulation } from "~/services/simulations/runner";
|
||||
import { syncStandings } from "~/services/standings-sync/index";
|
||||
import {
|
||||
getPendingStandingsMappings,
|
||||
|
|
@ -280,6 +281,17 @@ export async function action(args: Route.ActionArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
if (intent === "simulate") {
|
||||
try {
|
||||
await runSportsSeasonSimulation(params.id);
|
||||
return redirect(`/admin/sports-seasons/${params.id}/expected-values`);
|
||||
} catch (error) {
|
||||
return {
|
||||
simulateError: error instanceof Error ? error.message : "Simulation failed",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Update
|
||||
const name = formData.get("name");
|
||||
const year = formData.get("year");
|
||||
|
|
@ -669,7 +681,8 @@ export default function EditSportsSeason({ loaderData, actionData }: Route.Compo
|
|||
</Button>
|
||||
)}
|
||||
{simulatorInfo && (
|
||||
<Form method="post" action={`/admin/sports-seasons/${sportsSeason.id}/simulate`}>
|
||||
<Form method="post">
|
||||
<input type="hidden" name="intent" value="simulate" />
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
|
|
@ -702,6 +715,11 @@ export default function EditSportsSeason({ loaderData, actionData }: Route.Compo
|
|||
</>
|
||||
: "Import futures odds to set probability distributions."}
|
||||
</p>
|
||||
{"simulateError" in (actionData ?? {}) && actionData?.simulateError && (
|
||||
<div className="mt-3 bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
||||
{actionData.simulateError}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue