diff --git a/app/db/tables.ts b/app/db/tables.ts index cb1f9b133..894aa11ac 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -456,6 +456,8 @@ export interface TournamentSettings { roundCount: number; }; minMembersPerTeam?: number; + /** Maximum number of team members that can be registered (only applies to 4v4 tournaments) */ + maxMembersPerTeam?: number; isTest?: boolean; } diff --git a/app/features/calendar/CalendarRepository.server.ts b/app/features/calendar/CalendarRepository.server.ts index fe784c390..a94af6189 100644 --- a/app/features/calendar/CalendarRepository.server.ts +++ b/app/features/calendar/CalendarRepository.server.ts @@ -477,6 +477,7 @@ type CreateArgs = Pick< mapPickingStyle: Tables["Tournament"]["mapPickingStyle"]; bracketProgression: TournamentSettings["bracketProgression"] | null; minMembersPerTeam?: number; + maxMembersPerTeam?: number; teamsPerGroup?: number; thirdPlaceMatch?: boolean; requireInGameNames?: boolean; @@ -525,6 +526,7 @@ export async function create(args: CreateArgs) { regClosesAt: args.regClosesAt, requireInGameNames: args.requireInGameNames, minMembersPerTeam: args.minMembersPerTeam, + maxMembersPerTeam: args.maxMembersPerTeam, swiss: args.swissGroupCount && args.swissRoundCount ? { @@ -725,6 +727,7 @@ async function updateTournamentTables( regClosesAt: args.regClosesAt, requireInGameNames: args.requireInGameNames, minMembersPerTeam: args.minMembersPerTeam, + maxMembersPerTeam: args.maxMembersPerTeam, swiss: args.swissGroupCount && args.swissRoundCount ? { diff --git a/app/features/calendar/actions/calendar.new.server.ts b/app/features/calendar/actions/calendar.new.server.ts index 82829fb81..41a65c726 100644 --- a/app/features/calendar/actions/calendar.new.server.ts +++ b/app/features/calendar/actions/calendar.new.server.ts @@ -79,6 +79,7 @@ export const action: ActionFunction = async ({ request }) => { rankedModesShort.find((mode) => mode === data.toToolsMode) ?? null, bracketProgression: data.bracketProgression ?? null, minMembersPerTeam: data.minMembersPerTeam ?? undefined, + maxMembersPerTeam: data.maxMembersPerTeam ?? undefined, isRanked: data.isRanked ?? undefined, isTest: data.isTest ?? undefined, isInvitational: data.isInvitational ?? false, diff --git a/app/features/calendar/calendar-schemas.server.ts b/app/features/calendar/calendar-schemas.server.ts index a57296c37..a62448a10 100644 --- a/app/features/calendar/calendar-schemas.server.ts +++ b/app/features/calendar/calendar-schemas.server.ts @@ -86,7 +86,14 @@ export const newCalendarEventActionSchema = z checkboxValueToBoolean, z.boolean().nullish(), ), - minMembersPerTeam: z.coerce.number().int().min(1).max(4).nullish(), + minMembersPerTeam: z.preprocess( + actualNumber, + z.number().int().min(1).max(4).nullish(), + ), + maxMembersPerTeam: z.preprocess( + actualNumber, + z.number().int().min(4).max(10).nullish(), + ), bracketProgression: bracketProgressionSchema.nullish(), }) .refine( diff --git a/app/features/calendar/routes/calendar.new.tsx b/app/features/calendar/routes/calendar.new.tsx index c6e313eca..b5e29b0cd 100644 --- a/app/features/calendar/routes/calendar.new.tsx +++ b/app/features/calendar/routes/calendar.new.tsx @@ -1257,25 +1257,59 @@ function MapPoolValidationStatusMessage({ function MemberCountSelect() { const baseEvent = useBaseEvent(); const id = React.useId(); + const [memberCount, setMemberCount] = React.useState( + baseEvent?.tournament?.ctx.settings.minMembersPerTeam ?? 4, + ); + + return ( + <> +
+ + +
+ {memberCount === 4 ? : null} + + ); +} + +function MaxTeamMemberCountInput() { + const baseEvent = useBaseEvent(); + const id = React.useId(); return (
- + className="input__extra-small" + /> + + Maximum number of players that can be registered per team. Doesn't apply + to tournament organizers. Default is 6. +
); } diff --git a/app/features/tournament-bracket/core/Tournament.ts b/app/features/tournament-bracket/core/Tournament.ts index 574d373b5..f52b1b40e 100644 --- a/app/features/tournament-bracket/core/Tournament.ts +++ b/app/features/tournament-bracket/core/Tournament.ts @@ -863,24 +863,15 @@ export class Tournament { } /** what is the max amount of members teams can add in total? This limit doesn't apply to the organizer adding members to a team. */ - get maxTeamMemberCount() { + get maxMembersPerTeam() { // special format if (this.minMembersPerTeam !== 4) return this.minMembersPerTeam; - if (this.isLeagueSignup || this.isLeagueDivision) return 8; - - // TODO: retire this hack by making it user configurable - if (this.ctx.organization?.id === 19 && this.ctx.name.includes("FLUTI")) { - return 8; + if (this.ctx.settings.maxMembersPerTeam) { + return this.ctx.settings.maxMembersPerTeam; } - const maxMembersBeforeStart = 6; - - if (this.hasStarted) { - return maxMembersBeforeStart + 1; - } - - return maxMembersBeforeStart; + return 6; } /** Is the regular check-in (check-in for the whole tournament) open at this time? */ diff --git a/app/features/tournament-bracket/routes/to.$id.brackets.tsx b/app/features/tournament-bracket/routes/to.$id.brackets.tsx index 5c3dbd65b..d9b53c385 100644 --- a/app/features/tournament-bracket/routes/to.$id.brackets.tsx +++ b/app/features/tournament-bracket/routes/to.$id.brackets.tsx @@ -386,7 +386,7 @@ function AddSubsPopOver() { } const subsAvailableToAdd = - tournament.maxTeamMemberCount - ownedTeam.members.length; + tournament.maxMembersPerTeam - ownedTeam.members.length; const inviteLink = `${SENDOU_INK_BASE_URL}${tournamentJoinPage({ tournamentId: tournament.ctx.id, diff --git a/app/features/tournament/actions/to.$id.join.server.ts b/app/features/tournament/actions/to.$id.join.server.ts index c8a9fdfa1..4dda63789 100644 --- a/app/features/tournament/actions/to.$id.join.server.ts +++ b/app/features/tournament/actions/to.$id.join.server.ts @@ -68,7 +68,7 @@ export const action: ActionFunction = async ({ request, params }) => { inviteCode, teamToJoin, userId: user.id, - maxTeamSize: tournament.maxTeamMemberCount, + maxTeamSize: tournament.maxMembersPerTeam, }) === "VALID", "Cannot join this team or invite code is invalid", ); diff --git a/app/features/tournament/routes/to.$id.join.tsx b/app/features/tournament/routes/to.$id.join.tsx index b17d80bce..1a2e5b94f 100644 --- a/app/features/tournament/routes/to.$id.join.tsx +++ b/app/features/tournament/routes/to.$id.join.tsx @@ -28,7 +28,7 @@ export default function JoinTeamPage() { inviteCode: data.inviteCode, teamToJoin, userId: user?.id, - maxTeamSize: tournament.maxTeamMemberCount, + maxTeamSize: tournament.maxMembersPerTeam, }); const textPrompt = () => { diff --git a/app/features/tournament/routes/to.$id.register.tsx b/app/features/tournament/routes/to.$id.register.tsx index 426e95d15..e57f9f363 100644 --- a/app/features/tournament/routes/to.$id.register.tsx +++ b/app/features/tournament/routes/to.$id.register.tsx @@ -971,7 +971,7 @@ function FillRoster({ ); const optionalMembers = Math.max( - tournament.maxTeamMemberCount - ownTeamMembers.length - missingMembers, + tournament.maxMembersPerTeam - ownTeamMembers.length - missingMembers, 0, ); @@ -992,7 +992,7 @@ function FillRoster({ }); })(); - const teamIsFull = ownTeamMembers.length >= tournament.maxTeamMemberCount; + const teamIsFull = ownTeamMembers.length >= tournament.maxMembersPerTeam; const canAddMembers = !teamIsFull && tournament.registrationOpen; return ( @@ -1085,7 +1085,7 @@ function FillRoster({
{t("tournament:pre.roster.footer", { atLeastCount: tournament.minMembersPerTeam, - maxCount: tournament.maxTeamMemberCount, + maxCount: tournament.maxMembersPerTeam, })}
)} diff --git a/app/styles/common.css b/app/styles/common.css index 5cb49c7df..01de61509 100644 --- a/app/styles/common.css +++ b/app/styles/common.css @@ -81,14 +81,17 @@ input:user-invalid { .input__extra-small { min-width: var(--input-width-extra-small); + max-width: var(--input-width-extra-small); } .input__small { min-width: var(--input-width-small); + max-width: var(--input-width-small); } .input__medium { min-width: var(--input-width-medium); + max-width: var(--input-width-medium); } input:not(.plain, .combobox-input):focus-within { diff --git a/scripts/create-league-divisions.ts b/scripts/create-league-divisions.ts index 9f96c505a..5fc52820a 100644 --- a/scripts/create-league-divisions.ts +++ b/scripts/create-league-divisions.ts @@ -93,6 +93,7 @@ async function main() { autonomousSubs: false, isRanked: tournament.ctx.settings.isRanked, minMembersPerTeam: tournament.ctx.settings.minMembersPerTeam, + maxMembersPerTeam: tournament.ctx.settings.maxMembersPerTeam, regClosesAt: tournament.ctx.settings.regClosesAt, requireInGameNames: tournament.ctx.settings.requireInGameNames, bracketUrl: "https://sendou.ink",