Add chat to tournament matches

This commit is contained in:
Kalle
2022-04-03 10:35:07 +03:00
parent 61d16d43a5
commit 1287fa4ad9
8 changed files with 98 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
import clsx from "clsx";
import { ChatLoaderData } from "~/routes/chat";
import { Unpacked } from "~/utils";
import { Unpacked, ValueOf } from "~/utils";
import { ChatProps } from ".";
export function Message({
data,
@@ -9,7 +10,7 @@ export function Message({
}: {
data: Omit<Unpacked<ChatLoaderData["messages"]>, "roomId" | "id">;
sending?: boolean;
user: { info?: string; name: string };
user: ValueOf<ChatProps["users"]>;
}) {
return (
<li className="chat__message">

View File

@@ -7,13 +7,12 @@ import { SpeechBubbleIcon } from "../icons/SpeechBubble";
import { Message } from "./Message";
import useChat from "./useChat";
export function Chat({
id,
users,
}: {
export interface ChatProps {
id: string;
users: { [id: string]: { info?: string; name: string } };
}) {
users: { [id: string]: { info?: string | null; name: string } };
}
export function Chat({ id, users }: ChatProps) {
const user = useUser();
const {
messages,

View File

@@ -3,6 +3,7 @@ import invariant from "tiny-invariant";
import type { BracketModified } from "~/services/bracket";
import type { FindTournamentByNameForUrlI } from "~/services/tournament";
import { Unpacked } from "~/utils";
import { Chat } from "../Chat";
import { SubmitButton } from "../SubmitButton";
import { ActionSectionWrapper } from "./ActionSectionWrapper";
import { DuringMatchActionsRosters } from "./DuringMatchActionsRosters";
@@ -44,41 +45,60 @@ export function DuringMatchActions({
];
return (
<div className="tournament-bracket__during-match-actions">
<FancyStageBanner
stage={stage}
roundNumber={currentPosition}
infos={roundInfos}
>
{currentPosition > 1 && (
<Form method="post">
<input type="hidden" name="_action" value="UNDO_REPORT_SCORE" />
<input type="hidden" name="position" value={currentPosition - 1} />
<input type="hidden" name="matchId" value={currentMatch.id} />
<div className="tournament-bracket__stage-banner__bottom-bar">
<SubmitButton
actionType="UNDO_REPORT_SCORE"
className="tournament-bracket__stage-banner__undo-button"
loadingText="Undoing..."
>
Undo last score
</SubmitButton>
</div>
</Form>
<>
<Chat
key={currentMatch.id}
id={currentMatch.id}
users={Object.fromEntries(
[...ownTeam.members, ...opponentTeam.members].map((m) => [
m.member.id,
{
name: m.member.discordName,
info: m.member.friendCode,
},
])
)}
</FancyStageBanner>
<ActionSectionWrapper>
<DuringMatchActionsRosters
// Without the key prop when switching to another match the winnerId is remembered
// which causes "No winning team matching the id" error.
// Switching the key props forces the component to remount.
key={currentMatch.id}
ownTeam={ownTeam}
opponentTeam={opponentTeam}
matchId={currentMatch.id}
position={currentPosition}
/>
</ActionSectionWrapper>
</div>
/>
<div className="tournament-bracket__during-match-actions">
<FancyStageBanner
stage={stage}
roundNumber={currentPosition}
infos={roundInfos}
>
{currentPosition > 1 && (
<Form method="post">
<input type="hidden" name="_action" value="UNDO_REPORT_SCORE" />
<input
type="hidden"
name="position"
value={currentPosition - 1}
/>
<input type="hidden" name="matchId" value={currentMatch.id} />
<div className="tournament-bracket__stage-banner__bottom-bar">
<SubmitButton
actionType="UNDO_REPORT_SCORE"
className="tournament-bracket__stage-banner__undo-button"
loadingText="Undoing..."
>
Undo last score
</SubmitButton>
</div>
</Form>
)}
</FancyStageBanner>
<ActionSectionWrapper>
<DuringMatchActionsRosters
// Without the key prop when switching to another match the winnerId is remembered
// which causes "No winning team matching the id" error.
// Switching the key props forces the component to remount.
key={currentMatch.id}
ownTeam={ownTeam}
opponentTeam={opponentTeam}
matchId={currentMatch.id}
position={currentPosition}
/>
</ActionSectionWrapper>
</div>
</>
);
}

View File

@@ -82,6 +82,7 @@ export async function findByNameForUrl({
discordName: true,
discordId: true,
discordDiscriminator: true,
friendCode: true,
},
},
},

View File

@@ -26,6 +26,7 @@ import { useUser } from "~/hooks/common";
import tournamentStylesUrl from "../../styles/tournament.css";
import * as React from "react";
import { isTournamentAdmin } from "~/core/tournament/validators";
import { chatRoute } from "~/utils/urls";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: tournamentStylesUrl }];
@@ -81,6 +82,7 @@ export const meta: MetaFunction = (props) => {
};
export const unstable_shouldReload: ShouldReloadFunction = (data) => {
if (data.submission?.action === chatRoute()) return false;
const action = data.submission?.formData.get("_action");
if (!action) return true;
return !["REPORT_SCORE", "UNDO_REPORT_SCORE"].includes(String(action));

View File

@@ -1,4 +1,9 @@
import { LinksFunction, LoaderFunction, Outlet } from "remix";
import {
LinksFunction,
LoaderFunction,
Outlet,
ShouldReloadFunction,
} from "remix";
import { ActionFunction, json, useMatches } from "remix";
import invariant from "tiny-invariant";
import { z } from "zod";
@@ -17,6 +22,7 @@ import {
} from "~/utils";
import { useUser } from "~/hooks/common";
import { useBracketDataWithEvents } from "~/hooks/useBracketDataWithEvents";
import { chatRoute } from "~/utils/urls";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
@@ -104,13 +110,15 @@ export const action: ActionFunction = async ({
}
};
const typedJson = (args: BracketModified) => json(args);
export const unstable_shouldReload: ShouldReloadFunction = (data) => {
return data.submission?.action !== chatRoute();
};
export const loader: LoaderFunction = async ({ params }) => {
invariant(typeof params.bid === "string", "Expected params.bid to be string");
const bracket = await bracketById(params.bid);
return typedJson(bracket);
return json<BracketModified>(bracket);
};
// TODO: make bracket a bit smaller
@@ -124,18 +132,17 @@ export default function BracketTabWrapper() {
team.members.some(({ member }) => member.id === user?.id)
);
const winnersRounds = data.rounds.filter((round) => round.side === "winners");
const losersRounds = data.rounds.filter((round) => round.side === "losers");
return (
<div className="tournament-bracket__container">
<Outlet />
<BracketActions data={data} />
<EliminationBracket
rounds={data.rounds.filter((round) => round.side === "winners")}
ownTeamName={ownTeam?.name}
/>
<EliminationBracket
rounds={data.rounds.filter((round) => round.side === "losers")}
ownTeamName={ownTeam?.name}
/>
<EliminationBracket rounds={winnersRounds} ownTeamName={ownTeam?.name} />
{losersRounds.length > 0 ? (
<EliminationBracket rounds={losersRounds} ownTeamName={ownTeam?.name} />
) : null}
</div>
);
}

View File

@@ -182,6 +182,8 @@ export type Unpacked<T> = T extends (infer U)[]
? U
: T;
export type ValueOf<T> = T[keyof T];
export type MyReducerAction<
K extends string,
T extends Record<string, unknown> | undefined = undefined

View File

@@ -147,7 +147,7 @@ export async function seed(variation?: SeedVariations) {
userIds: string[],
loggedInUserId: string
) {
const userIdsCopy = [...userIds];
const userIdsCopy = [...userIds].filter((id) => id !== NZAP_UUID);
const mockTeams = [
"Team Olive",
"Chimera",
@@ -234,6 +234,17 @@ export async function seed(variation?: SeedVariations) {
},
});
}
if (team.name === "Last Minute") {
await prisma.tournamentTeamMember.create({
data: {
memberId: NZAP_UUID,
teamId: team.id,
captain: false,
tournamentId,
},
});
}
}
}