mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-12 05:35:16 -05:00
* Got something going * Style overwrites * width != height * More playing with lines * Migrations * Start bracket initial * Unhardcode stage generation params * Link to match page * Matches page initial * Support directly adding seed to map list generator * Add docs * Maps in matches page * Add invariant about tie breaker map pool * Fix PICNIC lacking tie breaker maps * Only link in bracket when tournament has started * Styled tournament roster inputs * Prefer IGN in tournament match page * ModeProgressIndicator * Some conditional rendering * Match action initial + better error display * Persist bestOf in DB * Resolve best of ahead of time * Move brackets-manager to core * Score reporting works * Clear winner on score report * ModeProgressIndicator: highlight winners * Fix inconsistent input * Better text when submitting match * mapCountPlayedInSetWithCertainty that works * UNDO_REPORT_SCORE implemented * Permission check when starting tournament * Remove IGN from upsert * View match results page * Source in DB * Match page waiting for teams * Move tournament bracket to feature folder * REOPEN_MATCH initial * Handle proper resetting of match * Inline bracket-manager * Syncify * Transactions * Handle match is locked gracefully * Match page auto refresh * Fix match refresh called "globally" * Bracket autoupdate * Move fillWithNullTillPowerOfTwo to utils with testing * Fix map lists not visible after tournament started * Optimize match events * Show UI while in progress to members * Fix start tournament alert not being responsive * Teams can check in * Fix map list 400 * xxx -> TODO * Seeds page * Remove map icons for team page * Don't display link to seeds after tournament has started * Admin actions initial * Change captain admin action * Make all hooks ts * Admin actions functioning * Fix validate error not displaying in CatchBoundary * Adjust validate args order * Remove admin loader * Make delete team button menancing * Only include checked in teams to bracket * Optimize to.id route loads * Working show map list generator toggle * Update full tournaments flow * Make full tournaments work with many start times * Handle undefined in crud * Dynamic stage banner * Handle default strat if map list generation fails * Fix crash on brackets if less than 2 teams * Add commented out test for reference * Add TODO * Add players from team during register * TrustRelationship * Prefers not to host feature * Last before merge * Rename some vars * More renames
138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import type {
|
|
ActionFunction,
|
|
LinksFunction,
|
|
LoaderArgs,
|
|
} from "@remix-run/node";
|
|
import { redirect } from "@remix-run/node";
|
|
import { Form, useLoaderData } from "@remix-run/react";
|
|
import { Main } from "~/components/Main";
|
|
import { SubmitButton } from "~/components/SubmitButton";
|
|
import { INVITE_CODE_LENGTH } from "~/constants";
|
|
import { useTranslation } from "~/hooks/useTranslation";
|
|
import { requireUser } from "~/modules/auth";
|
|
import {
|
|
notFoundIfFalsy,
|
|
validate,
|
|
type SendouRouteHandle,
|
|
} from "~/utils/remix";
|
|
import { teamPage } from "~/utils/urls";
|
|
import { addNewTeamMember } from "../queries/addNewTeamMember.server";
|
|
import { findByIdentifier } from "../queries/findByIdentifier.server";
|
|
import { inviteCodeById } from "../queries/inviteCodeById.server";
|
|
import { teamParamsSchema } from "../team-schemas.server";
|
|
import type { DetailedTeam } from "../team-types";
|
|
import { isTeamFull, isTeamMember } from "../team-utils";
|
|
import styles from "../team.css";
|
|
|
|
export const links: LinksFunction = () => {
|
|
return [{ rel: "stylesheet", href: styles }];
|
|
};
|
|
|
|
export const action: ActionFunction = async ({ request, params }) => {
|
|
const user = await requireUser(request);
|
|
const { customUrl } = teamParamsSchema.parse(params);
|
|
|
|
const { team } = notFoundIfFalsy(findByIdentifier(customUrl));
|
|
|
|
const inviteCode = new URL(request.url).searchParams.get("code") ?? "";
|
|
const realInviteCode = inviteCodeById(team.id)!;
|
|
|
|
validate(
|
|
validateInviteCode({
|
|
inviteCode,
|
|
realInviteCode,
|
|
team,
|
|
user,
|
|
}) === "VALID",
|
|
"Invite code is invalid"
|
|
);
|
|
|
|
addNewTeamMember({ teamId: team.id, userId: user.id });
|
|
|
|
return redirect(teamPage(team.customUrl));
|
|
};
|
|
|
|
export const handle: SendouRouteHandle = {
|
|
i18n: ["team"],
|
|
};
|
|
|
|
export const loader = async ({ request, params }: LoaderArgs) => {
|
|
const user = await requireUser(request);
|
|
const { customUrl } = teamParamsSchema.parse(params);
|
|
|
|
const { team } = notFoundIfFalsy(findByIdentifier(customUrl));
|
|
|
|
const inviteCode = new URL(request.url).searchParams.get("code") ?? "";
|
|
const realInviteCode = inviteCodeById(team.id)!;
|
|
|
|
const validation = validateInviteCode({
|
|
inviteCode,
|
|
realInviteCode,
|
|
team,
|
|
user,
|
|
});
|
|
|
|
if (validation === "ALREADY_JOINED") {
|
|
throw redirect(teamPage(team.customUrl));
|
|
}
|
|
|
|
return { validation, teamName: team.name };
|
|
};
|
|
|
|
function validateInviteCode({
|
|
inviteCode,
|
|
realInviteCode,
|
|
team,
|
|
user,
|
|
}: {
|
|
inviteCode: string;
|
|
realInviteCode: string;
|
|
team: DetailedTeam;
|
|
user?: { id: number; team?: { name: string } };
|
|
}) {
|
|
if (inviteCode.length !== INVITE_CODE_LENGTH) {
|
|
return "SHORT_CODE";
|
|
}
|
|
if (inviteCode !== realInviteCode) {
|
|
return "INVITE_CODE_WRONG";
|
|
}
|
|
if (isTeamFull(team)) {
|
|
return "TEAM_FULL";
|
|
}
|
|
if (isTeamMember({ team, user })) {
|
|
return "ALREADY_JOINED";
|
|
}
|
|
if (user?.team) {
|
|
return "ALREADY_IN_DIFFERENT_TEAM";
|
|
}
|
|
|
|
return "VALID";
|
|
}
|
|
|
|
export default function JoinTeamPage() {
|
|
const { t } = useTranslation(["team", "common"]);
|
|
const { validation, teamName } = useLoaderData<{
|
|
// not sure why using typeof loader here results validation in being typed as "string"
|
|
validation:
|
|
| "SHORT_CODE"
|
|
| "INVITE_CODE_WRONG"
|
|
| "TEAM_FULL"
|
|
| "ALREADY_IN_DIFFERENT_TEAM"
|
|
| "VALID";
|
|
teamName: string;
|
|
}>();
|
|
|
|
return (
|
|
<Main>
|
|
<Form method="post" className="team__invite-container">
|
|
<div className="text-center">
|
|
{t(`team:validation.${validation}`, { teamName })}
|
|
</div>
|
|
{validation === "VALID" ? (
|
|
<SubmitButton size="big">{t("common:actions.join")}</SubmitButton>
|
|
) : null}
|
|
</Form>
|
|
</Main>
|
|
);
|
|
}
|