Subs registration closing when regular registration closes feature

This commit is contained in:
Kalle
2024-03-24 10:48:28 +02:00
parent c6ea2b6e4b
commit 085d717217
5 changed files with 90 additions and 13 deletions

View File

@@ -12,7 +12,10 @@ import {
} from "~/features/tournament/tournament-utils";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import type { ModeShort } from "~/modules/in-game-lists";
import { databaseTimestampToDate } from "~/utils/dates";
import {
databaseTimestampToDate,
dateToDatabaseTimestamp,
} from "~/utils/dates";
import { fillWithNullTillPowerOfTwo } from "../tournament-bracket-utils";
import type { Stage } from "~/modules/brackets-model";
import { Bracket } from "./Bracket";
@@ -636,6 +639,17 @@ export class Tournament {
return !this.isInvitational();
}
get canAddNewSubPost() {
if (!this.subsFeatureEnabled) return false;
return (
!this.ctx.settings.regClosesAt ||
this.ctx.settings.regClosesAt ===
dateToDatabaseTimestamp(this.ctx.startTime) ||
this.registrationOpen
);
}
get maxTeamMemberCount() {
const maxMembersBeforeStart = this.isInvitational()
? 5

View File

@@ -17,12 +17,17 @@ import { Button } from "~/components/Button";
import { TrashIcon } from "~/components/icons/Trash";
import { findSubsByTournamentId } from "../queries/findSubsByTournamentId.server";
import { tournamentIdFromParams } from "~/features/tournament";
import { parseRequestFormData, type SendouRouteHandle } from "~/utils/remix";
import {
parseRequestFormData,
validate,
type SendouRouteHandle,
} from "~/utils/remix";
import { FormMessage } from "~/components/FormMessage";
import { subSchema } from "../tournament-subs-schemas.server";
import { upsertSub } from "../queries/upsertSub.server";
import { tournamentSubsPage } from "~/utils/urls";
import { useUser } from "~/features/auth/core/user";
import { tournamentFromDB } from "~/features/tournament-bracket/core/Tournament.server";
import "../tournament-subs.css";
@@ -37,8 +42,13 @@ export const action: ActionFunction = async ({ params, request }) => {
schema: subSchema,
});
const tournamentId = tournamentIdFromParams(params);
const tournament = await tournamentFromDB({ tournamentId, user });
// TODO: validate tournament is not finalized
validate(!tournament.everyBracketOver, "Tournament is over");
validate(
tournament.canAddNewSubPost,
"Registration is closed or subs feature disabled",
);
upsertSub({
bestWeapons: data.bestWeapons.join(","),
@@ -56,6 +66,11 @@ export const action: ActionFunction = async ({ params, request }) => {
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const tournament = await tournamentFromDB({ tournamentId, user });
if (!tournament.canAddNewSubPost) {
throw redirect(tournamentSubsPage(tournamentId));
}
const sub = findSubsByTournamentId({ tournamentId }).find(
(sub) => sub.userId === user.id,

View File

@@ -27,16 +27,30 @@ import {
findSubsByTournamentId,
type SubByTournamentId,
} from "../queries/findSubsByTournamentId.server";
import { parseRequestFormData, validate } from "~/utils/remix";
import { deleteSubSchema } from "../tournament-subs-schemas.server";
import { Popover } from "~/components/Popover";
import "../tournament-subs.css";
export const action: ActionFunction = async ({ request, params }) => {
const user = await requireUser(request);
const tournamentId = tournamentIdFromParams(params);
const tournament = await tournamentFromDB({ tournamentId, user });
const data = await parseRequestFormData({
request,
schema: deleteSubSchema,
});
validate(
user.id === data.userId || tournament.isOrganizer(user),
"You can only delete your own sub post",
401,
);
deleteSub({
tournamentId,
userId: user.id,
userId: data.userId,
});
return null;
@@ -84,7 +98,6 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
export default function TournamentSubsPage() {
const user = useUser();
const { t } = useTranslation(["tournament"]);
const data = useLoaderData<typeof loader>();
const tournament = useTournament();
@@ -96,11 +109,7 @@ export default function TournamentSubsPage() {
<div className="stack lg">
{!tournament.teamMemberOfByUser(user) && user ? (
<div className="stack items-end">
<LinkButton to="new" size="tiny">
{data.hasOwnSubPost
? t("tournament:subs.editPost")
: t("tournament:subs.addPost")}
</LinkButton>
<AddOrEditSubButton />
</div>
) : null}
{data.subs.map((sub) => {
@@ -110,9 +119,36 @@ export default function TournamentSubsPage() {
);
}
function AddOrEditSubButton() {
const { t } = useTranslation(["tournament"]);
const data = useLoaderData<typeof loader>();
const tournament = useTournament();
const buttonText = data.hasOwnSubPost
? t("tournament:subs.editPost")
: t("tournament:subs.addPost");
if (!tournament.canAddNewSubPost) {
return (
<Popover buttonChildren={<>{buttonText}</>} triggerClassName="tiny">
{data.hasOwnSubPost
? "Sub post can't be edited anymore since registration has closed"
: "Sub post can't be added anymore since registration has closed"}
</Popover>
);
}
return (
<LinkButton to="new" size="tiny">
{buttonText}
</LinkButton>
);
}
function SubInfoSection({ sub }: { sub: SubByTournamentId }) {
const { t } = useTranslation(["common", "tournament"]);
const user = useUser();
const tournament = useTournament();
const infos = [
<div key="vc" className="sub__section__info__vc">
@@ -172,9 +208,16 @@ function SubInfoSection({ sub }: { sub: SubByTournamentId }) {
<div className="sub__section__message">{sub.message}</div>
) : null}
</section>
{user?.id === sub.userId ? (
{user?.id === sub.userId || tournament.isOrganizer(user) ? (
<div className="stack mt-1 items-end">
<FormWithConfirm dialogHeading="Delete your sub post?">
<FormWithConfirm
dialogHeading={
user?.id === sub.userId
? "Delete your sub post?"
: `Delete sub post by ${sub.discordName}?`
}
fields={[["userId", sub.userId]]}
>
<Button
variant="minimal-destructive"
size="tiny"

View File

@@ -2,6 +2,7 @@ import { z } from "zod";
import {
checkboxValueToDbBoolean,
dbBoolean,
id,
processMany,
removeDuplicates,
safeJSONParse,
@@ -39,3 +40,7 @@ export const subSchema = z.object({
message: z.string().max(TOURNAMENT_SUB.MESSAGE_MAX_LENGTH).nullish(),
visibility: z.enum(["+1", "+2", "+3", "ALL"]).default("ALL"),
});
export const deleteSubSchema = z.object({
userId: id,
});

View File

@@ -356,7 +356,7 @@ export default function TournamentRegisterPage() {
) : (
<RegistrationForms />
)}
{!tournament.teamMemberOfByUser(user) && tournament.subsFeatureEnabled ? (
{!tournament.teamMemberOfByUser(user) && tournament.canAddNewSubPost ? (
<Link
to={tournamentSubsPage(tournament.ctx.id)}
className="text-xs text-center"