diff --git a/app/models/commissioner.ts b/app/models/commissioner.ts index 6d7a55b..e17a558 100644 --- a/app/models/commissioner.ts +++ b/app/models/commissioner.ts @@ -50,6 +50,16 @@ export async function isCommissioner( return !!commissioner; } +export async function countCommissionersByLeagueId( + leagueId: string +): Promise { + const db = database(); + const commissioners = await db.query.commissioners.findMany({ + where: eq(schema.commissioners.leagueId, leagueId), + }); + return commissioners.length; +} + export async function deleteCommissioner(id: string): Promise { const db = database(); await db.delete(schema.commissioners).where(eq(schema.commissioners.id, id)); diff --git a/app/routes/leagues/$leagueId.settings.tsx b/app/routes/leagues/$leagueId.settings.tsx index 6227078..2b1b702 100644 --- a/app/routes/leagues/$leagueId.settings.tsx +++ b/app/routes/leagues/$leagueId.settings.tsx @@ -4,7 +4,13 @@ import { getAuth } from "@clerk/react-router/server"; import { format } from "date-fns"; import { CalendarIcon } from "lucide-react"; import { findLeagueById, updateLeague, deleteLeague } from "~/models/league"; -import { isCommissioner } from "~/models/commissioner"; +import { + isCommissioner, + findCommissionersByLeagueId, + createCommissioner, + countCommissionersByLeagueId, + removeCommissionerByLeagueAndUser, +} from "~/models/commissioner"; import { findCurrentSeasonWithSports, updateSeason } from "~/models/season"; import { findTeamsBySeasonId, createManyTeams, deleteTeam, removeTeamOwner, assignTeamOwner } from "~/models/team"; import { findAllUsers, findUserByClerkId, isUserAdminByClerkId } from "~/models/user"; @@ -92,8 +98,20 @@ export async function loader(args: Route.LoaderArgs) { // Check if user is admin const isAdmin = await isUserAdminByClerkId(userId); - // Get all users if admin (for team assignment dropdown) - const allUsers = isAdmin ? await findAllUsers() : []; + // Get all users (for commissioner and team assignment dropdowns) + const allUsers = await findAllUsers(); + + // Get commissioners for this league with user info + const commissioners = await findCommissionersByLeagueId(leagueId); + const commissionerUserData = await Promise.all( + commissioners.map(async (c) => { + const user = await findUserByClerkId(c.userId); + return { + ...c, + userName: user?.username || user?.displayName || "Unknown User", + }; + }) + ); // Get owner details for teams const ownerIds = teams @@ -125,6 +143,7 @@ export async function loader(args: Route.LoaderArgs) { isAdmin, allUsers, ownerMap: Object.fromEntries(ownerMap), + commissioners: commissionerUserData, }; } @@ -471,11 +490,55 @@ export async function action(args: Route.ActionArgs) { } } + if (intent === "add-commissioner") { + const userClerkId = formData.get("userClerkId") as string; + + if (!userClerkId) { + return { error: "User is required" }; + } + + // Check if user is already a commissioner + const alreadyCommissioner = await isCommissioner(leagueId, userClerkId); + if (alreadyCommissioner) { + return { error: "This user is already a commissioner" }; + } + + try { + await createCommissioner({ leagueId, userId: userClerkId }); + return { success: true, message: "Commissioner added successfully" }; + } catch (error) { + console.error("Error adding commissioner:", error); + return { error: "Failed to add commissioner. Please try again." }; + } + } + + if (intent === "remove-commissioner") { + const commissionerUserId = formData.get("commissionerUserId") as string; + + if (!commissionerUserId) { + return { error: "User is required" }; + } + + // Prevent removing the last commissioner + const count = await countCommissionersByLeagueId(leagueId); + if (count <= 1) { + return { error: "Cannot remove the last commissioner" }; + } + + try { + await removeCommissionerByLeagueAndUser(leagueId, commissionerUserId); + return { success: true, message: "Commissioner removed successfully" }; + } catch (error) { + console.error("Error removing commissioner:", error); + return { error: "Failed to remove commissioner. Please try again." }; + } + } + return { error: "Invalid action" }; } export default function LeagueSettings({ loaderData, actionData }: Route.ComponentProps) { - const { league, season, teams, teamCount, teamsWithOwners, allSportsSeasons, draftSlots, isAdmin, allUsers, ownerMap } = loaderData; + const { league, season, teams, teamCount, teamsWithOwners, allSportsSeasons, draftSlots, isAdmin, allUsers, ownerMap, commissioners } = loaderData; const navigation = useNavigation(); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedSports, setSelectedSports] = useState>( @@ -1047,6 +1110,84 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone + {/* Commissioner Management */} + + + Commissioner Management + + Commissioners can manage league settings and oversee the draft. They do not need to own a team. + + + +
+ {commissioners.map((commissioner: any) => ( +
+
+

{commissioner.userName}

+ {!teams.some((t: any) => t.ownerId === commissioner.userId) && ( +

No team in current season

+ )} +
+ {commissioners.length > 1 && ( +
+ + + +
+ )} +
+ ))} +
+ +
+

Add Commissioner

+
+ + + +
+
+
+
+ {/* Danger Zone */} diff --git a/app/routes/leagues/$leagueId.tsx b/app/routes/leagues/$leagueId.tsx index 121eb2b..17fe447 100644 --- a/app/routes/leagues/$leagueId.tsx +++ b/app/routes/leagues/$leagueId.tsx @@ -373,6 +373,9 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
{commissioners.map((commissioner) => { const commissionerName = commissionerMap[commissioner.userId]; + const hasTeam = teams.some( + (t) => t.ownerId === commissioner.userId + ); return (
)}

+ {!hasTeam && ( +

+ No team +

+ )}
); })} diff --git a/app/routes/leagues/new.tsx b/app/routes/leagues/new.tsx index e3bb3e5..7ee7110 100644 --- a/app/routes/leagues/new.tsx +++ b/app/routes/leagues/new.tsx @@ -194,11 +194,12 @@ export async function action(args: Route.ActionArgs) { } // Create teams with fun random names + const joinAsPlayer = formData.get("joinAsPlayer") === "on"; const teamNames = generateUniqueTeamNames(teamCountNum); const teams = teamNames.map((name, i) => ({ seasonId: season.id, name, - ownerId: i === 0 ? userId : null, // Assign first team to creator + ownerId: joinAsPlayer && i === 0 ? userId : null, })); await createManyTeams(teams); @@ -485,6 +486,17 @@ export default function NewLeague({ loaderData, actionData }: Route.ComponentPro
+
+ + +
+ {actionData?.error && (
{actionData.error}