brackt/app/routes/admin.sports-seasons.$id.events.tsx
Chris Parsons 1584d34b89
Redesign to dark-mode-only with navy palette and accent colors (#13)
Removes light mode entirely in favour of a permanent dark theme with a
navy-tinted background and three signature accents (electric blue,
amber/gold, coral) exposed as CSS custom properties and Tailwind
utilities (bg-electric, text-amber-accent, text-coral-accent).

- Set class="dark" on <html> and apply Clerk dark base theme
- Rewrite app.css: single :root palette (oklch navy values), custom
  --electric / --amber-accent / --coral-accent variables, remove
  duplicate .dark block and light-mode bg-white/bg-gray-950 rule
- Install @clerk/themes for Clerk dark modal support
- Replace hardcoded Tailwind colors across 30+ files:
  - Draft grid cells: blue-50/blue-950 → electric/15, green-50/950 → emerald/10
  - Timer: green-600/yellow-600/red-600 → emerald-400/amber-accent/coral-accent
  - Status badges: blue-50/green-50/gray-50 → electric/emerald/muted variants
  - Success messages: green-500/15 text-green-700 dark:text-green-400 → emerald-500/15 text-emerald-400
  - Info cards: blue-50 dark:bg-blue-950 → electric/10
  - Warning cards: yellow-500 → amber-accent variants
  - Medal/placement badges: yellow-500/orange-600 → amber-accent/coral-accent
  - Movement indicators: green-600/red-600 → emerald-400/coral-accent
  - Connection dots: green-500/red-500 → emerald-500/coral-accent
- Remove dark:hidden/dark:block logo toggle in welcome.tsx (always dark)
- Update DraftGrid test assertions to match new class names

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 19:26:11 -08:00

241 lines
9.2 KiB
TypeScript

import { Form, Link } from "react-router";
import type { Route } from "./+types/admin.sports-seasons.$id.events";
import { loader, action } from "./admin.sports-seasons.$id.events.server";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { Badge } from "~/components/ui/badge";
import { Calendar, Trophy, ArrowLeft, Trash2 } from "lucide-react";
import { format } from "date-fns";
import { QualifyingPointsStandings } from "~/components/scoring/QualifyingPointsStandings";
export { loader, action };
export default function SportsSeasonEvents({
loaderData,
actionData,
}: Route.ComponentProps) {
const { sportsSeason, events, qpStandings, scoringRules } = loaderData;
const getEventTypeLabel = (type: string) => {
switch (type) {
case "playoff_game":
return "Bracket";
case "major_tournament":
return "Major Tournament";
case "final_standings":
return "Final Standings";
default:
return type;
}
};
const getStatusBadge = (isComplete: boolean) => {
return isComplete ? (
<Badge variant="default" className="bg-emerald-500">
Completed
</Badge>
) : (
<Badge variant="secondary">In Progress</Badge>
);
};
return (
<div className="p-8">
<div className="max-w-4xl">
<div className="mb-6">
<Button variant="ghost" size="sm" asChild className="mb-2">
<Link to={`/admin/sports-seasons/${sportsSeason.id}`}>
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Sports Season
</Link>
</Button>
<h1 className="text-3xl font-bold">Scoring Events</h1>
<p className="text-muted-foreground mt-1">
{sportsSeason.sport.name} - {sportsSeason.name}
</p>
{sportsSeason.scoringPattern === "qualifying_points" && (
<div className="mt-2">
<Badge variant="outline" className="bg-amber-50 text-amber-700 border-amber-300 dark:bg-amber-950 dark:text-amber-300">
Qualifying Points Mode
</Badge>
</div>
)}
</div>
<div className="space-y-6">
{/* Success/Error Messages */}
{actionData?.error && (
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
{actionData.error}
</div>
)}
{actionData?.success && (
<div className="bg-emerald-500/15 text-emerald-400 px-4 py-3 rounded-md text-sm">
{actionData.success}
</div>
)}
{/* QP Standings for qualifying points sports */}
{sportsSeason.scoringPattern === "qualifying_points" && qpStandings && (
<QualifyingPointsStandings
standings={qpStandings}
scoringRules={scoringRules}
isFinalized={sportsSeason.qualifyingPointsFinalized || false}
totalMajors={sportsSeason.totalMajors}
majorsCompleted={sportsSeason.majorsCompleted}
canFinalize={
sportsSeason.totalMajors !== null &&
sportsSeason.majorsCompleted >= sportsSeason.totalMajors &&
!sportsSeason.qualifyingPointsFinalized
}
/>
)}
{/* Create New Event Card */}
<Card>
<CardHeader>
<CardTitle>Create New Event</CardTitle>
<CardDescription>
Add a new scoring event (bracket, tournament, standings, etc.)
</CardDescription>
</CardHeader>
<CardContent>
<Form method="post" className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Event Name</Label>
<Input
id="name"
name="name"
type="text"
placeholder="e.g., Super Bowl, Round 1, Game 3"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="eventType">Event Type</Label>
<Select
name="eventType"
defaultValue={sportsSeason.scoringPattern === "qualifying_points" ? "major_tournament" : "playoff_game"}
required
>
<SelectTrigger id="eventType">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="playoff_game">Bracket</SelectItem>
<SelectItem value="major_tournament">Major Tournament</SelectItem>
<SelectItem value="final_standings">Final Standings</SelectItem>
</SelectContent>
</Select>
{sportsSeason.scoringPattern === "qualifying_points" && (
<p className="text-sm text-muted-foreground">
Major Tournament events will automatically award qualifying points when completed.
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="eventDate">Event Date (Optional)</Label>
<Input id="eventDate" name="eventDate" type="date" />
</div>
{actionData?.error && (
<div className="bg-destructive/15 text-destructive px-4 py-3 rounded-md text-sm">
{actionData.error}
</div>
)}
<Button type="submit" className="w-full">
<Trophy className="mr-2 h-4 w-4" />
Create Event
</Button>
</Form>
</CardContent>
</Card>
{/* Events List */}
<Card>
<CardHeader>
<CardTitle>Events</CardTitle>
<CardDescription>
{events.length} {events.length === 1 ? "event" : "events"}
</CardDescription>
</CardHeader>
<CardContent>
{events.length === 0 ? (
<p className="text-sm text-muted-foreground">
No events created yet. Create your first event above.
</p>
) : (
<div className="space-y-3">
{events.map((event: { id: string; name: string; eventType: string; eventDate?: string | null; isComplete: boolean }) => (
<Card key={event.id} className="hover:border-primary/50 transition-colors">
<CardContent className="pt-6">
<div className="flex items-start justify-between gap-4">
<Link
to={`/admin/sports-seasons/${sportsSeason.id}/events/${event.id}`}
className="flex-1 cursor-pointer"
>
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold">{event.name}</h3>
{getStatusBadge(event.isComplete)}
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span>
{getEventTypeLabel(event.eventType)}
</span>
{event.eventDate && (
<span className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
{format(new Date(event.eventDate), "MMM d, yyyy")}
</span>
)}
</div>
</Link>
<Form method="post" className="flex-shrink-0">
<input type="hidden" name="intent" value="delete-event" />
<input type="hidden" name="eventId" value={event.id} />
<Button
type="submit"
size="sm"
variant="ghost"
className="text-destructive hover:text-destructive"
onClick={(e) => {
if (!confirm(`Delete event "${event.name}"? This will also delete all results.`)) {
e.preventDefault();
}
}}
>
<Trash2 className="h-4 w-4" />
</Button>
</Form>
</div>
</CardContent>
</Card>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
);
}