- Add app/lib/logger.ts: dev passes through to console; prod routes errors to Sentry.captureException and warnings to Sentry.captureMessage, with extra context preserved. Uses captureMessage (not captureException) for string-only args to avoid fabricated stack traces. - Add server/logger.ts: dev passes through; prod silences log/info but keeps warn/error on stderr (Sentry not initialized in that process). - Replace all console.* calls across 44 app files and 4 server files. - Upgrade no-console from warn → error in oxlint; exempt logger files and scripts/** via overrides. - Add typescript/no-inferrable-types rule; fix violations in services and simulators. Exempt test files (intentional string widening for switch/if tests would break under literal type inference). Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
150 lines
4.9 KiB
TypeScript
150 lines
4.9 KiB
TypeScript
import { Form, Link, redirect } from "react-router";
|
|
import type { Route } from "./+types/admin.templates.new";
|
|
|
|
import { logger } from "~/lib/logger";
|
|
import { createSeasonTemplate } from "~/models/season-template";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Input } from "~/components/ui/input";
|
|
import { Label } from "~/components/ui/label";
|
|
import { Textarea } from "~/components/ui/textarea";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "~/components/ui/card";
|
|
|
|
export function meta(): Route.MetaDescriptors {
|
|
return [{ title: "New Template - Brackt Admin" }];
|
|
}
|
|
|
|
export async function action({ request }: Route.ActionArgs) {
|
|
const formData = await request.formData();
|
|
const name = formData.get("name");
|
|
const year = formData.get("year");
|
|
const description = formData.get("description");
|
|
|
|
// Validation
|
|
if (typeof name !== "string" || !name.trim()) {
|
|
return { error: "Template name is required" };
|
|
}
|
|
|
|
if (typeof year !== "string") {
|
|
return { error: "Year is required" };
|
|
}
|
|
|
|
const yearNum = parseInt(year, 10);
|
|
if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
|
|
return { error: "Year must be between 2000 and 2100" };
|
|
}
|
|
|
|
try {
|
|
const template = await createSeasonTemplate({
|
|
name: name.trim(),
|
|
year: yearNum,
|
|
description: typeof description === "string" && description.trim() ? description.trim() : null,
|
|
isActive: true,
|
|
});
|
|
|
|
return redirect(`/admin/templates/${template.id}`);
|
|
} catch (error) {
|
|
logger.error("Error creating template:", error);
|
|
return { error: "Failed to create template. Please try again." };
|
|
}
|
|
}
|
|
|
|
export default function NewTemplate({ actionData }: Route.ComponentProps) {
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
return (
|
|
<div className="p-8">
|
|
<div className="max-w-2xl">
|
|
<div className="mb-6">
|
|
<h1 className="text-3xl font-bold">Create Season Template</h1>
|
|
<p className="text-muted-foreground mt-1">
|
|
Bundle sports seasons together for league commissioners
|
|
</p>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Template Details</CardTitle>
|
|
<CardDescription>
|
|
Create a template that commissioners can use when setting up their leagues
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form method="post" className="space-y-6">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">Template Name</Label>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
type="text"
|
|
placeholder="e.g., 2025 Full Season, 2025 Playoffs Only"
|
|
required
|
|
/>
|
|
<p className="text-sm text-muted-foreground">
|
|
A descriptive name that commissioners will see
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="year">Year</Label>
|
|
<Input
|
|
id="year"
|
|
name="year"
|
|
type="number"
|
|
min="2000"
|
|
max="2100"
|
|
defaultValue={currentYear}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="description">Description (Optional)</Label>
|
|
<Textarea
|
|
id="description"
|
|
name="description"
|
|
placeholder="Describe what sports seasons are included in this template"
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
{actionData?.error && (
|
|
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
|
|
{actionData.error}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-4">
|
|
<Button type="submit" className="flex-1">
|
|
Create Template
|
|
</Button>
|
|
<Button type="button" variant="outline" asChild>
|
|
<Link to="/admin/templates">Cancel</Link>
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="mt-6 border-electric/30 bg-electric/10">
|
|
<CardHeader>
|
|
<CardTitle className="text-electric">Next Steps</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="text-sm text-foreground/80">
|
|
<p>After creating the template, you'll be able to:</p>
|
|
<ul className="list-disc list-inside mt-2 space-y-1">
|
|
<li>Add sports seasons to the template</li>
|
|
<li>Mark which sports are required vs optional</li>
|
|
<li>Set the number of flex spots</li>
|
|
</ul>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|