Auto-name teams on claim and harden owner-assignment logic (#163)
* Auto-name teams on claim and harden owner-assignment logic - Add claimTeam() model function that atomically sets ownerId + name in a single DB write, replacing the previous two-step assignTeamOwner + renameTeam pattern that could leave partial state on failure - Rename team to "Team <username>" (fallback: displayName, then "Member") when a user joins via invite link or an admin assigns a team owner - Fetch the user record before claiming the team in both paths so we fail fast with a clear error rather than silently skipping the rename - Guard against assigning an already-owned team in the admin settings action (closes #107, closes #76) - Show owner username below team name in draft order settings UI; teams with an owner but no matching user record display "Unknown" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fixing dropdown for admins --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
41384f08fb
commit
9d1765d8a4
3 changed files with 42 additions and 26 deletions
|
|
@ -83,6 +83,14 @@ export async function assignTeamOwner(
|
|||
return await updateTeam(id, { ownerId });
|
||||
}
|
||||
|
||||
export async function claimTeam(
|
||||
id: string,
|
||||
ownerId: string,
|
||||
name: string
|
||||
): Promise<Team> {
|
||||
return await updateTeam(id, { ownerId, name });
|
||||
}
|
||||
|
||||
export async function removeTeamOwner(id: string): Promise<Team> {
|
||||
return await updateTeam(id, { ownerId: null });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import {
|
|||
findLeagueById,
|
||||
findCommissionersByLeagueId,
|
||||
findAvailableTeams,
|
||||
assignTeamOwner,
|
||||
claimTeam,
|
||||
isUserLeagueMember,
|
||||
findUserByClerkId,
|
||||
} from "~/models";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
|
|
@ -94,8 +95,16 @@ export async function action(args: Route.ActionArgs) {
|
|||
throw new Response("No available teams in this league", { status: 400 });
|
||||
}
|
||||
|
||||
// Assign the first available team to the user
|
||||
await assignTeamOwner(availableTeams[0].id, userId);
|
||||
// Look up user before claiming to fail fast and get their name
|
||||
const user = await findUserByClerkId(userId);
|
||||
if (!user) {
|
||||
throw new Response("User account not found. Please try again.", { status: 500 });
|
||||
}
|
||||
|
||||
// Claim the first available team, setting owner and name atomically
|
||||
const team = availableTeams[0];
|
||||
const teamName = `Team ${user.username || user.displayName || "Member"}`;
|
||||
await claimTeam(team.id, userId, teamName);
|
||||
|
||||
// Redirect to league page
|
||||
return redirect(`/leagues/${season.leagueId}?joined=true`);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
removeCommissionerByLeagueAndUser,
|
||||
} from "~/models/commissioner";
|
||||
import { findCurrentSeasonWithSports, updateSeason } from "~/models/season";
|
||||
import { findTeamsBySeasonId, deleteTeam, removeTeamOwner, assignTeamOwner } from "~/models/team";
|
||||
import { findTeamsBySeasonId, deleteTeam, removeTeamOwner, claimTeam } from "~/models/team";
|
||||
import { findAllUsers, findUserByClerkId, isUserAdminByClerkId } from "~/models/user";
|
||||
import { unlinkSportFromSeason, linkMultipleSportsToSeason } from "~/models/season-sport";
|
||||
import { findAllSportsSeasons } from "~/models/sports-season";
|
||||
|
|
@ -361,6 +361,12 @@ export async function action(args: Route.ActionArgs) {
|
|||
return { error: "Only admins can assign team owners" };
|
||||
}
|
||||
|
||||
// Look up user before assigning to fail fast and get their name
|
||||
const assignedUser = await findUserByClerkId(userClerkId);
|
||||
if (!assignedUser) {
|
||||
return { error: "User not found. Please try again." };
|
||||
}
|
||||
|
||||
// Check if user is already assigned to a team in this season
|
||||
const teams = await findTeamsBySeasonId(season.id);
|
||||
const userAlreadyHasTeam = teams.some(team => team.ownerId === userClerkId);
|
||||
|
|
@ -369,8 +375,16 @@ export async function action(args: Route.ActionArgs) {
|
|||
return { error: "This user is already assigned to a team in this league" };
|
||||
}
|
||||
|
||||
// Check if the target team already has an owner
|
||||
const targetTeam = teams.find(team => team.id === teamId);
|
||||
if (targetTeam?.ownerId) {
|
||||
return { error: "This team already has an owner. Remove the current owner first." };
|
||||
}
|
||||
|
||||
try {
|
||||
await assignTeamOwner(teamId, userClerkId);
|
||||
const teamName = `Team ${assignedUser.username || assignedUser.displayName || "Member"}`;
|
||||
await claimTeam(teamId, userClerkId, teamName);
|
||||
|
||||
return { success: true, message: "Owner assigned successfully" };
|
||||
} catch (error) {
|
||||
console.error("Error assigning team owner:", error);
|
||||
|
|
@ -1132,6 +1146,9 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">{team.name}</p>
|
||||
{team.ownerId && (
|
||||
<p className="text-xs text-muted-foreground">{ownerMap[team.ownerId] ?? "Unknown"}</p>
|
||||
)}
|
||||
</div>
|
||||
{canEditDraftOrder && (
|
||||
<div className="flex gap-1">
|
||||
|
|
@ -1349,24 +1366,7 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
<SelectValue placeholder="Select a user" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{isAdmin ? (
|
||||
allUsers.map((user) => {
|
||||
const alreadyCommissioner = commissioners.some(
|
||||
(c) => c.userId === user.clerkId
|
||||
);
|
||||
return (
|
||||
<SelectItem
|
||||
key={user.id}
|
||||
value={user.clerkId}
|
||||
disabled={alreadyCommissioner}
|
||||
>
|
||||
{user.username || user.displayName || user.email}
|
||||
{alreadyCommissioner && " (already commissioner)"}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
leagueMembers.map((member) => {
|
||||
{leagueMembers.map((member) => {
|
||||
const alreadyCommissioner = commissioners.some(
|
||||
(c) => c.userId === member.clerkId
|
||||
);
|
||||
|
|
@ -1380,8 +1380,7 @@ export default function LeagueSettings({ loaderData, actionData }: Route.Compone
|
|||
{alreadyCommissioner && " (already commissioner)"}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue