import { useState, useEffect } from "react"; import { Form, Link, redirect, useNavigation } 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 { findDraftSlotsBySeasonId, setDraftOrder, randomizeDraftOrder } from "~/models/draft-slot"; 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(); const draftSlots = season ? await findDraftSlotsBySeasonId(season.id) : []; // 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, draftSlots, }; } 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 === "set-draft-order") { if (season.status !== "pre_draft") { return { error: "Cannot modify draft order after draft has started" }; } const teams = await findTeamsBySeasonId(season.id); const teamIds = formData.getAll("teamOrder") as string[]; // Validate that all teams are included if (teamIds.length !== teams.length) { return { error: "All teams must be included in the draft order" }; } // Validate that all team IDs are valid const validTeamIds = new Set(teams.map(t => t.id)); for (const teamId of teamIds) { if (!validTeamIds.has(teamId)) { return { error: "Invalid team ID in draft order" }; } } await setDraftOrder(season.id, teamIds); return { success: true, message: "Draft order updated successfully" }; } if (intent === "randomize-draft-order") { if (season.status !== "pre_draft") { return { error: "Cannot modify draft order after draft has started" }; } const teams = await findTeamsBySeasonId(season.id); const teamIds = teams.map(t => t.id); await randomizeDraftOrder(season.id, teamIds); return { success: true, message: "Draft order randomized successfully" }; } 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, teams, teamCount, teamsWithOwners, allSportsSeasons, draftSlots } = loaderData; const navigation = useNavigation(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedSports, setSelectedSports] = useState>( new Set(season?.seasonSports?.map((s: any) => s.sportsSeason.id) || []) ); const [draftOrderTeams, setDraftOrderTeams] = useState( draftSlots.length > 0 ? draftSlots.map((slot: any) => slot.teamId) : teams.map((team: any) => team.id) ); // Update draft order when loader data changes (after randomization) useEffect(() => { const newOrder = draftSlots.length > 0 ? draftSlots.map((slot: any) => slot.teamId) : teams.map((team: any) => team.id); setDraftOrderTeams(newOrder); }, [draftSlots, teams]); const canEditSports = season && season.status === "pre_draft"; const canEditTeamCount = season && season.status === "pre_draft"; const canEditDraftOrder = 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 }; const moveDraftSlot = (fromIndex: number, toIndex: number) => { const newOrder = [...draftOrderTeams]; const [movedTeam] = newOrder.splice(fromIndex, 1); newOrder.splice(toIndex, 0, movedTeam); setDraftOrderTeams(newOrder); }; const getTeamById = (teamId: string) => { return teams.find((t: any) => t.id === teamId); }; 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}
)}
{/* Draft Order Management */} Draft Order {canEditDraftOrder ? "Set the draft order for your league. Drag teams to reorder or randomize." : "Draft order cannot be changed after the draft has started"}
{draftSlots.length === 0 && canEditDraftOrder && (

Note: Draft order has not been set yet. Use the form below to set the order manually or click "Randomize" to generate a random order.

)}
{draftOrderTeams.map((teamId, index) => { const team = getTeamById(teamId); if (!team) return null; return (
{index + 1}

{team.name}

{canEditDraftOrder && (
)}
); })}
{canEditDraftOrder && (
)} {actionData?.success && actionData?.message && (
{actionData.message}
)} {actionData?.error && (
{actionData.error}
)}
{canEditDraftOrder && (
)}
{/* 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
); }