diff --git a/app/features/tournament/actions/to.$id.register.server.ts b/app/features/tournament/actions/to.$id.register.server.ts index a20c9037b..1f220b7a6 100644 --- a/app/features/tournament/actions/to.$id.register.server.ts +++ b/app/features/tournament/actions/to.$id.register.server.ts @@ -144,6 +144,10 @@ export const action: ActionFunction = async ({ request, params }) => { } case "DELETE_TEAM_MEMBER": { errorToastIfFalsy(ownTeam, "You are not registered to this tournament"); + errorToastIfFalsy( + !tournament.isInvitational, + "The organizer manages the roster of an invitational team", + ); errorToastIfFalsy( ownTeam.memberUserIds.includes(data.userId), "User is not in your team", @@ -344,6 +348,10 @@ export const action: ActionFunction = async ({ request, params }) => { } case "UNREGISTER": { errorToastIfFalsy(ownTeam, "You are not registered to this tournament"); + errorToastIfFalsy( + !tournament.isInvitational, + "The organizer manages the roster of an invitational team", + ); errorToastIfFalsy( !ownTeamCheckedIn, "You cannot unregister after checking in", diff --git a/app/features/tournament/routes/to.$id.register.tsx b/app/features/tournament/routes/to.$id.register.tsx index 0f5984f0d..9bd30bf26 100644 --- a/app/features/tournament/routes/to.$id.register.tsx +++ b/app/features/tournament/routes/to.$id.register.tsx @@ -180,19 +180,12 @@ function RegistrationForms({ readOnly = false }: { readOnly?: boolean }) { const ownTeamCheckedIn = Boolean(ownTeam && ownTeam.checkIns.length > 0); const hasFriendCodeSet = Boolean(user?.friendCode); - if (!user && !tournament.isInvitational) { + if (!user) { return ; } - const showRegistrationProgress = () => { - if (ownTeam) return true; - - return !tournament.isInvitational; - }; - const showRegisterNewTeam = () => { if (ownTeam) return true; - if (tournament.isInvitational) return false; if (!tournament.registrationOpen) return false; return !tournament.regularCheckInHasEnded; @@ -202,24 +195,19 @@ function RegistrationForms({ readOnly = false }: { readOnly?: boolean }) {
{showRegisterNewTeam() ? : null} {hasFriendCodeSet ? ( - showRegistrationProgress() ? ( - - ) : ( - - This tournament is invitational. Tournament organizer adds all - teams. - - ) + ) : null} {showRegisterNewTeam() && hasFriendCodeSet ? ( ) : null} {tournament.isLeague && @@ -682,6 +670,7 @@ function FillRoster({ const showDeleteMemberSection = !readOnly && + !tournament.isInvitational && ((!ownTeamCheckedIn && ownTeamMembers.length > 1) || (ownTeamCheckedIn && ownTeamMembers.length > tournament.minMembersPerTeam)); diff --git a/changelog/2026-08-29-invitational-roster-kick.md b/changelog/2026-08-29-invitational-roster-kick.md new file mode 100644 index 000000000..52ed88369 --- /dev/null +++ b/changelog/2026-08-29-invitational-roster-kick.md @@ -0,0 +1,5 @@ +--- +navItem: calendar +type: bug +--- +Captains of an invitational tournament team can no longer kick members or unregister the team on their own diff --git a/e2e/pages/tournament/tournament-register-page.ts b/e2e/pages/tournament/tournament-register-page.ts index 9b1e6166c..1e85f06b6 100644 --- a/e2e/pages/tournament/tournament-register-page.ts +++ b/e2e/pages/tournament/tournament-register-page.ts @@ -28,6 +28,8 @@ export class TournamentRegisterPage { "Registration for this tournament has closed", ), leaveTeamButton: page.getByRole("button", { name: "Leave the team" }), + deleteMemberButton: page.getByRole("button", { name: "Delete member" }), + unregisterButton: page.getByRole("button", { name: "Unregister" }), organizerAddedLeaveExplanation: page.getByText( "You were added to the team by the organizer. Contact the TO to leave the team.", ), diff --git a/e2e/tournament-invitational.spec.ts b/e2e/tournament-invitational.spec.ts index 3b960bc8f..e1d21a777 100644 --- a/e2e/tournament-invitational.spec.ts +++ b/e2e/tournament-invitational.spec.ts @@ -85,6 +85,13 @@ test.describe("Invitational tournament", () => { await expect( register.locators.organizerAddedLeaveExplanation, ).toBeVisible(); + + // nor can the captain kick them, the organizer owns the roster + await impersonate(page, captain.id); + await register.goto(tournament.id); + await expect(register.member(2)).toBeVisible(); + await expect(register.locators.deleteMemberButton).toHaveCount(0); + await expect(register.locators.unregisterButton).toHaveCount(0); }); });