mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 07:37:55 -05:00
Add chat to tournament matches
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function findByNameForUrl({
|
||||
discordName: true,
|
||||
discordId: true,
|
||||
discordDiscriminator: true,
|
||||
friendCode: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user