import { useState } from "react"; import { Form, Link, redirect } from "react-router"; import { getAuth } from "@clerk/react-router/server"; import { findLeagueById, updateLeague, deleteLeague } from "~/models/league"; import { isCommissioner } from "~/models/commissioner"; import { findCurrentSeasonWithSports } from "~/models/season"; import { findTeamsBySeasonId, createManyTeams, deleteTeam } from "~/models/team"; import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport"; import { findAllSportsSeasons } from "~/models/sports-season"; import type { Route } from "./+types/$leagueId.settings"; 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 { Checkbox } from "~/components/ui/checkbox"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "~/components/ui/alert-dialog"; export async function loader(args: Route.LoaderArgs) { const { userId } = await getAuth(args); const { params } = args; const { leagueId } = params; if (!userId) { throw new Response("Unauthorized", { status: 401 }); } // Fetch league const league = await findLeagueById(leagueId); if (!league) { throw new Response("League not found", { status: 404 }); } // Check if user is authorized (creator or commissioner) const userIsCommissioner = await isCommissioner(leagueId, userId); const isCreator = league.createdBy === userId; if (!isCreator && !userIsCommissioner) { throw new Response("Forbidden - You must be a commissioner to access settings", { status: 403, }); } // Get current season with sports const season = await findCurrentSeasonWithSports(leagueId); const teams = season ? await findTeamsBySeasonId(season.id) : []; const allSportsSeasons = await findAllSportsSeasons(); // Count teams with owners const teamsWithOwners = teams.filter(team => team.ownerId !== null).length; return { league, season, teams, teamCount: teams.length, teamsWithOwners, allSportsSeasons: allSportsSeasons as Array, }; } export async function action(args: Route.ActionArgs) { const { userId } = await getAuth(args); const { params, request } = args; const { leagueId } = params; if (!userId) { throw new Response("Unauthorized", { status: 401 }); } // Verify authorization const league = await findLeagueById(leagueId); if (!league) { throw new Response("League not found", { status: 404 }); } const userIsCommissioner = await isCommissioner(leagueId, userId); const isCreator = league.createdBy === userId; if (!isCreator && !userIsCommissioner) { throw new Response("Forbidden", { status: 403 }); } const formData = await request.formData(); const intent = formData.get("intent"); // Get current season to check status const season = await findCurrentSeasonWithSports(leagueId); if (!season) { return { error: "No active season found" }; } if (intent === "update-sports") { if (season.status !== "pre_draft") { return { error: "Cannot modify sports after draft has started" }; } const selectedSports = formData.getAll("sportsSeasons"); const currentSportIds = new Set( season.seasonSports?.map((s: any) => s.sportsSeason.id) || [] ); const newSportIds = new Set(selectedSports as string[]); // Remove sports that are no longer selected for (const sportId of currentSportIds) { if (!newSportIds.has(sportId)) { await unlinkSportFromSeason(season.id, sportId); } } // Add newly selected sports const sportsToAdd = []; for (const sportId of newSportIds) { if (!currentSportIds.has(sportId)) { sportsToAdd.push({ seasonId: season.id, sportsSeasonId: sportId as string, }); } } if (sportsToAdd.length > 0) { await linkMultipleSportsToSeason(sportsToAdd); } return { success: true }; } if (intent === "delete") { // Delete the league await deleteLeague(leagueId); // Redirect to home with success message return redirect("/?deleted=true"); } if (intent === "update") { const name = formData.get("name"); const teamCount = formData.get("teamCount"); // Validation if (typeof name !== "string" || !name.trim()) { return { error: "League name is required" }; } if (name.trim().length < 3 || name.trim().length > 50) { return { error: "League name must be between 3 and 50 characters" }; } try { await updateLeague(leagueId, { name: name.trim(), }); // Handle team count changes if provided if (typeof teamCount === "string") { const newTeamCount = parseInt(teamCount, 10); if (!isNaN(newTeamCount)) { const teams = await findTeamsBySeasonId(season.id); const currentTeamCount = teams.length; const teamsWithOwners = teams.filter(t => t.ownerId !== null).length; // Validate team count if (newTeamCount < 6 || newTeamCount > 16) { return { error: "Number of teams must be between 6 and 16" }; } if (newTeamCount < teamsWithOwners) { return { error: `Cannot reduce team count below ${teamsWithOwners} (number of teams with owners)` }; } // Add or remove teams as needed if (newTeamCount > currentTeamCount) { // Add new teams const teamsToAdd = Array.from( { length: newTeamCount - currentTeamCount }, (_, i) => ({ seasonId: season.id, name: `Team ${currentTeamCount + i + 1}`, ownerId: null, }) ); await createManyTeams(teamsToAdd); } else if (newTeamCount < currentTeamCount) { // Remove teams without owners (from the end) const teamsToRemove = teams .filter(t => t.ownerId === null) .slice(-(currentTeamCount - newTeamCount)); for (const team of teamsToRemove) { await deleteTeam(team.id); } } } } return redirect(`/leagues/${leagueId}?updated=true`); } catch (error) { console.error("Error updating league:", error); return { error: "Failed to update league. Please try again." }; } } return { error: "Invalid action" }; } export default function LeagueSettings({ loaderData, actionData }: Route.ComponentProps) { const { league, season, teamCount, teamsWithOwners, allSportsSeasons } = loaderData; const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedSports, setSelectedSports] = useState>( new Set(season?.seasonSports?.map((s: any) => s.sportsSeason.id) || []) ); const canEditSports = season && season.status === "pre_draft"; const canEditTeamCount = season && season.status === "pre_draft"; const minTeamCount = Math.max(6, teamsWithOwners); const handleSportToggle = (sportId: string) => { const newSelected = new Set(selectedSports); if (newSelected.has(sportId)) { newSelected.delete(sportId); } else { newSelected.add(sportId); } setSelectedSports(newSelected); }; const handleDelete = () => { setIsDeleteDialogOpen(false); // The form submission will handle the actual deletion }; return (

League Settings

{league.name}

{/* Update League Form */} General Settings Update your league's basic information

{canEditTeamCount ? `Can be changed before draft starts (minimum ${minTeamCount} based on registered teams)` : "Team count cannot be changed after draft has started"}

{actionData?.error && (
{actionData.error}
)}
{/* Sports Seasons Management */} Sports Seasons {canEditSports ? "Select the sports seasons that teams can draft from" : "Sports cannot be modified after the draft has started"}

Recommendation: Select 16-20 sports seasons for optimal league balance and variety.

{allSportsSeasons.length > 0 ? ( allSportsSeasons.map((season) => (
handleSportToggle(season.id)} disabled={!canEditSports} />
)) ) : (

No sports seasons available

)}
{canEditSports && ( )} {actionData?.success && (
Sports updated successfully!
)} {actionData?.error && (
{actionData.error}
)}
{/* Danger Zone */} Danger Zone Irreversible and destructive actions Are you absolutely sure? This will permanently delete {league.name} and all associated data including seasons, teams, and commissioners. This action cannot be undone. Cancel
Delete League
); }