diff --git a/app/routes/to/$organization.$tournament.tsx b/app/routes/to/$organization.$tournament.tsx index d8a665a72..f8ee256f5 100644 --- a/app/routes/to/$organization.$tournament.tsx +++ b/app/routes/to/$organization.$tournament.tsx @@ -11,7 +11,7 @@ import { useLoaderData, Outlet, useLocation } from "remix"; import invariant from "tiny-invariant"; import { DiscordIcon } from "~/components/icons/Discord"; import { TwitterIcon } from "~/components/icons/Twitter"; -import { makeTitle } from "~/utils"; +import { getUser, makeTitle, requireUser } from "~/utils"; import { findTournamentByNameForUrl, FindTournamentByNameForUrlI, @@ -22,7 +22,7 @@ export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: tournamentStylesUrl }]; }; -export const loader: LoaderFunction = ({ params }) => { +export const loader: LoaderFunction = ({ params, context }) => { invariant( typeof params.organization === "string", "Expected params.organization to be string" @@ -32,9 +32,12 @@ export const loader: LoaderFunction = ({ params }) => { "Expected params.tournament to be string" ); + const user = getUser(context); + return findTournamentByNameForUrl({ organizationNameForUrl: params.organization, tournamentNameForUrl: params.tournament, + userId: user?.id, }); }; diff --git a/app/routes/to/$organization.$tournament/register.tsx b/app/routes/to/$organization.$tournament/register.tsx index f98898bbc..822a9ec22 100644 --- a/app/routes/to/$organization.$tournament/register.tsx +++ b/app/routes/to/$organization.$tournament/register.tsx @@ -1,23 +1,24 @@ -import styles from "~/styles/tournament-register.css"; +import { Prisma } from ".prisma/client"; +import * as React from "react"; import { - Form, - redirect, - LinksFunction, ActionFunction, - useMatches, - useTransition, - useNavigate, - useLocation, + Form, + LinksFunction, + redirect, useActionData, + useLocation, + useMatches, + useNavigate, + useTransition, } from "remix"; import invariant from "tiny-invariant"; +import ErrorMessage from "~/components/ErrorMessage"; import { createTournamentTeam, FindTournamentByNameForUrlI, } from "~/services/tournament"; -import { requireUser } from "~/utils"; -import { Prisma } from ".prisma/client"; -import ErrorMessage from "~/components/ErrorMessage"; +import styles from "~/styles/tournament-register.css"; +import { requireUser, useUser } from "~/utils"; export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; @@ -81,15 +82,28 @@ export const action: ActionFunction = async ({ return redirect("/"); }; -// TODO: redirect if not logged in - export default function RegisterPage() { const actionData = useActionData(); const transition = useTransition(); const [, parentRoute] = useMatches(); + const tournamentData = parentRoute.data as FindTournamentByNameForUrlI; const navigate = useNavigate(); const location = useLocation(); - const { id } = parentRoute.data as FindTournamentByNameForUrlI; + const user = useUser(); + + console.log("tournamentData", tournamentData); + + // TODO: redirect if tournament has concluded + + if (!user) { + return
pls log in lul
; + } + + const isAlreadyInTeam = tournamentData.teams.some((roster) => + roster.members.some(({ member }) => member.id === user.id) + ); + + if (isAlreadyInTeam) return ; return (
@@ -98,7 +112,11 @@ export default function RegisterPage() {
- + ); } + +function AddPlayersPage() { + const [, parentRoute] = useMatches(); + const tournamentData = parentRoute.data as FindTournamentByNameForUrlI; + const [urlWithInviteCode, setUrlWithInviteCode] = React.useState(""); + + const ownTeam = tournamentData.teams.find(({ inviteCode }) => + Boolean(inviteCode) + ); + + React.useEffect(() => { + if (ownTeam) { + setUrlWithInviteCode( + `${window.location.href.replace("/register", "")}?join=${ + ownTeam.inviteCode + }` + ); + } + }, []); + + // TODO: if not a captain of a team -> redirect + if (!ownTeam) return null; + + return ( + <> +
+
+ + +
+
+ + + +
+
+ + ); +} diff --git a/app/services/tournament.ts b/app/services/tournament.ts index b58d33496..0c7c831bc 100644 --- a/app/services/tournament.ts +++ b/app/services/tournament.ts @@ -10,9 +10,11 @@ export type FindTournamentByNameForUrlI = Serialized< export async function findTournamentByNameForUrl({ organizationNameForUrl, tournamentNameForUrl, + userId, }: { organizationNameForUrl: string; tournamentNameForUrl: string; + userId?: number; }) { const tournaments = await db.tournament.findMany({ where: { @@ -45,6 +47,7 @@ export async function findTournamentByNameForUrl({ checkedIn: true, id: true, name: true, + inviteCode: true, members: { select: { captain: true, @@ -64,13 +67,26 @@ export async function findTournamentByNameForUrl({ }, }); - const result = tournaments.find( + let result = tournaments.find( (tournament) => tournament.organizer.nameForUrl === organizationNameForUrl.toLowerCase() ); if (!result) throw json("Not Found", { status: 404 }); + result = { + ...result, + teams: result.teams.map((team) => ({ + ...team, + // Censor invite code if not captain of the team + inviteCode: team.members + .filter(({ captain }) => captain) + .some(({ member }) => member.id === userId) + ? team.inviteCode + : "", + })), + }; + result.organizer.twitter = twitterToUrl(result.organizer.twitter); result.organizer.discordInvite = discordInviteToUrl( result.organizer.discordInvite @@ -89,6 +105,13 @@ function discordInviteToUrl(discordInvite: string) { return `https://discord.com/invite/${discordInvite}`; } +function censorInviteCodeIfNotCaptain( + inviteCode: string, + members: { member: { id: number }; captain: boolean }[] +) { + return inviteCode; +} + export function createTournamentTeam({ userId, teamName, diff --git a/app/styles/tournament-register.css b/app/styles/tournament-register.css index 7575deccf..94ad695f6 100644 --- a/app/styles/tournament-register.css +++ b/app/styles/tournament-register.css @@ -20,3 +20,18 @@ gap: var(--s-2); margin-block-start: var(--s-6); } + +.tournament__invite-players__actions-container { + display: grid; + grid-template-columns: 1fr 1fr; + width: 100%; +} + +.tournament__invite-players__input { + width: min(100%, 45rem); + height: 10rem; +} + +.tournament__invite-players__input__copy-button { + margin-block-start: var(--s-2); +} diff --git a/app/styles/tournament.css b/app/styles/tournament.css index 6b140e452..ddf76685b 100644 --- a/app/styles/tournament.css +++ b/app/styles/tournament.css @@ -1,7 +1,9 @@ .tournament__container { + width: 100%; display: flex; flex-direction: column; align-items: center; + margin: 0 auto; } .tournament__links-container { @@ -173,7 +175,7 @@ } @media screen and (min-width: 768px) { - .info-banner { + .tournament__container { width: 48rem; } } diff --git a/app/utils/index.ts b/app/utils/index.ts index 2d22ac4a4..00de19c5a 100644 --- a/app/utils/index.ts +++ b/app/utils/index.ts @@ -2,6 +2,7 @@ import { json, useMatches } from "remix"; export const makeTitle = (endOfTitle: string) => `sendou.ink | ${endOfTitle}`; +/** Get logged in user from context. Throws with 401 error if no user found. */ export const requireUser = (ctx: any) => { const user = ctx.user; @@ -12,6 +13,13 @@ export const requireUser = (ctx: any) => { return user as NonNullable; }; +/** Get logged in user from context. Doesn't throw. */ +export const getUser = (ctx: any) => { + const user = ctx.user; + + return user as LoggedInUser; +}; + export type LoggedInUser = { id: number; discordId: string;