Register to tournament action

This commit is contained in:
Kalle (Sendou)
2021-11-28 14:21:55 +02:00
parent 801aa78e12
commit 72d89cef0a
11 changed files with 163 additions and 35 deletions

View File

@@ -15,6 +15,7 @@ import normalizeStylesUrl from "~/styles/normalize.css";
import globalStylesUrl from "~/styles/global.css";
import layoutStylesUrl from "~/styles/layout.css";
import { Layout } from "./components/Layout";
import type { LoggedInUser } from "./utils";
export const links: LinksFunction = () => {
return [
@@ -30,11 +31,7 @@ export const loader: LoaderFunction = ({ context }) => {
return user ?? null;
};
type LoggedInUser = {
id: number;
discordId: string;
discordAvatar: string;
} | null;
export let unstable_shouldReload = () => false;
const UserContext = React.createContext<LoggedInUser>(null);

View File

@@ -7,7 +7,7 @@ import {
NavLink,
Link,
} from "remix";
import { useLoaderData, Outlet } from "remix";
import { useLoaderData, Outlet, useLocation } from "remix";
import invariant from "tiny-invariant";
import { DiscordIcon } from "~/components/icons/Discord";
import { TwitterIcon } from "~/components/icons/Twitter";
@@ -49,30 +49,35 @@ export const meta: MetaFunction = (props) => {
export default function TournamentPage() {
const data = useLoaderData<FindTournamentByNameForUrlI>();
const location = useLocation();
const displayNavLinks = !location.pathname.endsWith("register");
return (
<div className="tournament__container">
<InfoBanner />
<div
style={{ "--tabs-count": 5 } as Record<string, number>}
className="tournament__links-container"
>
<NavLink className="tournament__nav-link" to="overview">
Overview
</NavLink>
<NavLink className="tournament__nav-link" to="map-pool">
Map Pool
</NavLink>
<NavLink className="tournament__nav-link" to="bracket">
Bracket
</NavLink>
<NavLink className="tournament__nav-link" to="teams">
Teams ({data.teams.length})
</NavLink>
<NavLink className="tournament__nav-link" to="streams">
Streams (4)
</NavLink>
</div>
{displayNavLinks && (
<div
style={{ "--tabs-count": 5 } as Record<string, number>}
className="tournament__links-container"
>
<NavLink className="tournament__nav-link" to="overview">
Overview
</NavLink>
<NavLink className="tournament__nav-link" to="map-pool">
Map Pool
</NavLink>
<NavLink className="tournament__nav-link" to="bracket">
Bracket
</NavLink>
<NavLink className="tournament__nav-link" to="teams">
Teams ({data.teams.length})
</NavLink>
<NavLink className="tournament__nav-link" to="streams">
Streams (4)
</NavLink>
</div>
)}
<Outlet />
</div>
);

View File

@@ -1,4 +1,4 @@
import stylesUrl from "~/styles/map-pool.css";
import styles from "~/styles/tournament-map-pool.css";
import type { Mode } from ".prisma/client";
import classNames from "classnames";
import { modesShort, stages } from "~/constants";
@@ -6,7 +6,7 @@ import { LinksFunction, useMatches } from "remix";
import { FindTournamentByNameForUrlI } from "~/services/tournament";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: stylesUrl }];
return [{ rel: "stylesheet", href: styles }];
};
export default function MapPoolTab() {

View File

@@ -0,0 +1,87 @@
import styles from "~/styles/tournament-register.css";
import {
Form,
redirect,
LinksFunction,
ActionFunction,
useMatches,
} from "remix";
import invariant from "tiny-invariant";
import {
createTournamentTeam,
FindTournamentByNameForUrlI,
} from "~/services/tournament";
import { requireUser } from "~/utils";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
const TEAM_NAME_MIN_LENGTH = 2;
const TEAM_NAME_MAX_LENGTH = 40;
export const action: ActionFunction = async ({ request, context }) => {
const formData = await request.formData();
const teamName = formData.get("team-name");
const tournamentId = Number(formData.get("tournament-id"));
invariant(typeof teamName === "string", "Invalid type for team name.");
invariant(
typeof tournamentId === "number",
"Invalid type for tournament id."
);
if (
teamName.length < TEAM_NAME_MIN_LENGTH ||
teamName.length > TEAM_NAME_MAX_LENGTH
) {
throw new Error(
`Team name length has to be between min ${TEAM_NAME_MIN_LENGTH} and max ${TEAM_NAME_MAX_LENGTH}. Was ${teamName.length}.`
);
}
const user = requireUser(context);
// TODO: validate can register for tournament
await createTournamentTeam({
teamName,
tournamentId,
userId: user.id,
});
// TODO: redirect to add players to page
return redirect("/");
};
// TODO: redirect if not logged in
export default function RegisterPage() {
const [, parentRoute] = useMatches();
const { id } = parentRoute.data as FindTournamentByNameForUrlI;
return (
<div className="tournament__register__container">
<h2 className="tournament__register__header">Register now</h2>
<div className="tournament__register__content">
<Form method="post">
<label htmlFor="team-name">Team name</label>
<input type="hidden" name="tournament-id" value={id} />
<input
name="team-name"
id="team-name"
required
minLength={TEAM_NAME_MIN_LENGTH}
maxLength={TEAM_NAME_MAX_LENGTH}
/>
{/* <ErrorMessage error={errors["team-name"]} /> */}
<div className="tournament__register__buttons-container">
<button type="submit">Submit</button>
<button className="outlined" type="button">
Cancel
</button>
</div>
</Form>
</div>
</div>
);
}

View File

@@ -1,10 +1,10 @@
import { useMatches, LinksFunction } from "remix";
import type { FindTournamentByNameForUrlI } from "~/services/tournament";
import stylesUrl from "~/styles/teams.css";
import styles from "~/styles/tournament-teams.css";
import { Fragment } from "react";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: stylesUrl }];
return [{ rel: "stylesheet", href: styles }];
};
export default function TeamsTab() {

View File

@@ -91,16 +91,16 @@ function discordInviteToUrl(discordInvite: string) {
export function createTournamentTeam({
userId,
name,
teamName,
tournamentId,
}: {
userId: number;
name: string;
teamName: string;
tournamentId: number;
}) {
return prisma.tournamentTeam.create({
data: {
name: name.trim(),
name: teamName.trim(),
tournamentId,
members: {
create: {

View File

@@ -110,9 +110,8 @@ button.outlined {
input:not(.plain) {
height: 1rem;
padding: var(--s-4) var(--s-3);
border: none;
border: 1px solid var(--border);
background-color: var(--bg-lighter);
background-color: transparent;
border-radius: var(--rounded);
color: var(--text);
outline: none;

View File

@@ -0,0 +1,22 @@
.tournament__register__container {
padding-block-start: var(--s-4);
}
.tournament__register__header {
margin-top: 0;
font-size: var(--fonts-lg);
font-weight: var(--bold);
list-style: none;
user-select: none;
}
.tournament__register__content {
width: min(24rem, 100%);
margin-block-start: var(--s-4);
}
.tournament__register__buttons-container {
display: flex;
gap: var(--s-2);
margin-block-start: var(--s-6);
}

View File

@@ -1,5 +1,23 @@
import { json } from "remix";
export const makeTitle = (endOfTitle: string) => `sendou.ink | ${endOfTitle}`;
export const requireUser = (ctx: any) => {
const user = ctx.user;
if (!user) {
throw json("Log in required", { status: 401 });
}
return user as NonNullable<LoggedInUser>;
};
export type LoggedInUser = {
id: number;
discordId: string;
discordAvatar: string;
} | null;
export type Serialized<T> = {
[P in keyof T]: T[P] extends Date ? string : Serialized<T[P]>;
};