Add Teams Tab

This commit is contained in:
Kalle (Sendou)
2021-11-15 15:57:26 +02:00
parent b5b0923f54
commit 9394ffee5d
18 changed files with 508 additions and 33 deletions

View File

@@ -1,6 +1,7 @@
import { Routes as SolidAppRoutes, Route } from "solid-app-router";
import { lazy } from "solid-js";
import { MapPoolTab } from "./scenes/tournament/components/MapPoolTab";
import { TeamsTab } from "./scenes/tournament/components/TeamsTab";
import TournamentData from "./scenes/tournament/TournamentPage.data";
const TournamentsPage = lazy(
@@ -18,7 +19,7 @@ export function Routes() {
>
<Route path="/map-pool" element={<MapPoolTab />} />
<Route path="/bracket" element={() => <>bracket</>} />
<Route path="/teams" element={() => <>teams</>} />
<Route path="/teams" element={<TeamsTab />} />
<Route path="/streams" element={() => <>streams</>} />
<Route path="/*all" element={() => <>overview</>} />
</Route>

View File

@@ -0,0 +1,21 @@
import s from "../styles/AvatarWithName.module.css";
export function AvatarWithName(p: {
discordName: string;
discordAvatar: string | null;
discordId: string;
}) {
return (
<div class={s.container}>
<div class={s.placeholder}>
<img
alt=""
class={s.avatar}
loading="lazy"
src={`https://cdn.discordapp.com/avatars/${p.discordId}/${p.discordAvatar}.png?size=80`}
/>
</div>
<div>{p.discordName}</div>
</div>
);
}

151
package-lock.json generated
View File

@@ -22,8 +22,12 @@
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/faker": "^5.5.9",
"@types/node": "^16.11.7",
"concurrently": "^6.4.0",
"faker": "^5.5.3",
"just-shuffle": "^4.0.1",
"node-fetch": "^3.1.0",
"prisma": "^3.4.2",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",
@@ -611,6 +615,12 @@
"@types/range-parser": "*"
}
},
"node_modules/@types/faker": {
"version": "5.5.9",
"resolved": "https://registry.npmjs.org/@types/faker/-/faker-5.5.9.tgz",
"integrity": "sha512-uCx6mP3UY5SIO14XlspxsGjgaemrxpssJI0Ol+GfhxtcKpv9pgRZYsS4eeKeHVLje6Qtc8lGszuBI461+gVZBA==",
"dev": true
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -1011,6 +1021,15 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
"dev": true,
"engines": {
"node": ">= 12"
}
},
"node_modules/date-fns": {
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz",
@@ -1422,6 +1441,34 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/faker": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz",
"integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==",
"dev": true
},
"node_modules/fetch-blob": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz",
"integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -1452,6 +1499,18 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dev": true,
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1641,6 +1700,12 @@
"node": ">=6"
}
},
"node_modules/just-shuffle": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/just-shuffle/-/just-shuffle-4.0.1.tgz",
"integrity": "sha512-zi6tkAMmnrMc39Tmxma7uEJ4c/NmzreJYHNqpZZ5CR8DluhfarzSmAZ/Tr3HjCYJrXajX6L+rEmC2Z/Fq3uuyg==",
"dev": true
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -1746,6 +1811,24 @@
"node": ">= 0.6"
}
},
"node_modules/node-fetch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz",
"integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==",
"dev": true,
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.2",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
@@ -2288,6 +2371,15 @@
"vite": "^2.6.13"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -2833,6 +2925,12 @@
"@types/range-parser": "*"
}
},
"@types/faker": {
"version": "5.5.9",
"resolved": "https://registry.npmjs.org/@types/faker/-/faker-5.5.9.tgz",
"integrity": "sha512-uCx6mP3UY5SIO14XlspxsGjgaemrxpssJI0Ol+GfhxtcKpv9pgRZYsS4eeKeHVLje6Qtc8lGszuBI461+gVZBA==",
"dev": true
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -3153,6 +3251,12 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"data-uri-to-buffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
"dev": true
},
"date-fns": {
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz",
@@ -3424,6 +3528,21 @@
}
}
},
"faker": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz",
"integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==",
"dev": true
},
"fetch-blob": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz",
"integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==",
"dev": true,
"requires": {
"web-streams-polyfill": "^3.0.3"
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -3453,6 +3572,15 @@
}
}
},
"formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dev": true,
"requires": {
"fetch-blob": "^3.1.2"
}
},
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -3587,6 +3715,12 @@
"minimist": "^1.2.5"
}
},
"just-shuffle": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/just-shuffle/-/just-shuffle-4.0.1.tgz",
"integrity": "sha512-zi6tkAMmnrMc39Tmxma7uEJ4c/NmzreJYHNqpZZ5CR8DluhfarzSmAZ/Tr3HjCYJrXajX6L+rEmC2Z/Fq3uuyg==",
"dev": true
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -3665,6 +3799,17 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-fetch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz",
"integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==",
"dev": true,
"requires": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.2",
"formdata-polyfill": "^4.0.10"
}
},
"node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
@@ -4058,6 +4203,12 @@
"vite": "^2.6.13"
}
},
"web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
"dev": true
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

View File

@@ -28,8 +28,12 @@
"devDependencies": {
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/faker": "^5.5.9",
"@types/node": "^16.11.7",
"concurrently": "^6.4.0",
"faker": "^5.5.3",
"just-shuffle": "^4.0.1",
"node-fetch": "^3.1.0",
"prisma": "^3.4.2",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",

View File

@@ -55,6 +55,24 @@ CREATE TABLE "Stage" (
CONSTRAINT "Stage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TournamentTeam" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"checkedIn" BOOLEAN NOT NULL DEFAULT false,
"tournamentId" INTEGER NOT NULL,
"inviteCode" TEXT NOT NULL,
CONSTRAINT "TournamentTeam_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TournamentTeamMember" (
"memberId" INTEGER NOT NULL,
"teamId" INTEGER NOT NULL,
"captain" BOOLEAN NOT NULL DEFAULT false
);
-- CreateTable
CREATE TABLE "_StageToTournament" (
"A" INTEGER NOT NULL,
@@ -73,6 +91,12 @@ CREATE UNIQUE INDEX "Organization_ownerId_key" ON "Organization"("ownerId");
-- CreateIndex
CREATE UNIQUE INDEX "Tournament_nameForUrl_organizerId_key" ON "Tournament"("nameForUrl", "organizerId");
-- CreateIndex
CREATE UNIQUE INDEX "TournamentTeam_name_tournamentId_key" ON "TournamentTeam"("name", "tournamentId");
-- CreateIndex
CREATE UNIQUE INDEX "TournamentTeamMember_memberId_teamId_key" ON "TournamentTeamMember"("memberId", "teamId");
-- CreateIndex
CREATE UNIQUE INDEX "_StageToTournament_AB_unique" ON "_StageToTournament"("A", "B");
@@ -85,6 +109,15 @@ ALTER TABLE "Organization" ADD CONSTRAINT "Organization_ownerId_fkey" FOREIGN KE
-- AddForeignKey
ALTER TABLE "Tournament" ADD CONSTRAINT "Tournament_organizerId_fkey" FOREIGN KEY ("organizerId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TournamentTeam" ADD CONSTRAINT "TournamentTeam_tournamentId_fkey" FOREIGN KEY ("tournamentId") REFERENCES "Tournament"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TournamentTeamMember" ADD CONSTRAINT "TournamentTeamMember_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "TournamentTeam"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TournamentTeamMember" ADD CONSTRAINT "TournamentTeamMember_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_StageToTournament" ADD FOREIGN KEY ("A") REFERENCES "Stage"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -8,8 +8,8 @@ datasource db {
}
model User {
id Int @id @default(autoincrement())
discordId String @unique
id Int @id @default(autoincrement())
discordId String @unique
discordName String
discordDiscriminator String
discordAvatar String?
@@ -18,9 +18,10 @@ model User {
twitter String?
youtubeId String?
youtubeName String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ownedOrganization Organization?
tournamentTeams TournamentTeamMember[]
}
model Organization {
@@ -36,7 +37,7 @@ model Organization {
}
model Tournament {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
name String
/// Name in lower case to show in URL
nameForUrl String
@@ -49,7 +50,9 @@ model Tournament {
bannerTextHSLArgs String
mapPool Stage[]
organizerId Int
organizer Organization @relation(fields: [organizerId], references: [id])
organizer Organization @relation(fields: [organizerId], references: [id])
teams TournamentTeam[]
// There might be duplicate nameForUrl's but inside an organization they're unique
@@unique([nameForUrl, organizerId])
}
@@ -68,3 +71,27 @@ model Stage {
mode Mode
tournamentMapPools Tournament[]
}
// TODO: how to check on database level only one player per team?
model TournamentTeam {
id Int @id @default(autoincrement())
name String
checkedIn Boolean @default(false)
tournamentId Int
tournament Tournament @relation(fields: [tournamentId], references: [id])
inviteCode String @default(uuid())
members TournamentTeamMember[]
@@unique([name, tournamentId])
}
model TournamentTeamMember {
memberId Int
teamId Int
captain Boolean @default(false)
team TournamentTeam @relation(fields: [teamId], references: [id])
member User @relation(fields: [memberId], references: [id])
@@unique([memberId, teamId])
}

View File

@@ -2,16 +2,25 @@ import pkg from "@prisma/client";
import { stages as stagesList } from "../utils/constants";
const { PrismaClient } = pkg;
const prisma = new PrismaClient();
import fetch from "node-fetch";
import shuffle from "just-shuffle";
import faker from "faker";
async function main() {
const user = await users();
const organization = await organizations(user.id);
const userCreated = await user();
await users();
const organization = await organizations(userCreated.id);
const tournament = await tournaments(organization.id);
const usersCreated = await prisma.user.findMany({});
await tournamentTeams(
tournament.id,
usersCreated.map((u) => u.id)
);
await stages();
await tournamentAddMaps(tournament.id);
}
async function users() {
async function user() {
return prisma.user.create({
data: {
discordDiscriminator: "4059",
@@ -27,6 +36,56 @@ async function users() {
});
}
async function users() {
const fetched = (await (
await fetch("https://sendou.ink/api/users")
).json()) as {
id: number;
username: string;
discriminator: string;
discordAvatar?: string;
discordId: string;
profile?: { twitterName?: string };
}[];
return prisma.user.createMany({
data: fetched
.filter((u) => u.discordId !== "79237403620945920")
.map((u) => ({
discordDiscriminator: u.discriminator,
discordId: u.discordId,
discordAvatar: u.discordAvatar,
discordRefreshToken: "none",
discordName: u.username,
twitter: u.profile?.twitterName,
})),
skipDuplicates: true,
});
}
async function tournamentTeams(tournamentId: number, users: number[]) {
const randomIds = shuffle(users);
for (let index = 0; index < 24; index++) {
const team = await prisma.tournamentTeam.create({
data: {
name: faker.address.cityName(),
tournamentId,
},
});
for (let index = 0; index < faker.datatype.number(6) + 1; index++) {
const memberId = randomIds.pop()!;
await prisma.tournamentTeamMember.create({
data: {
memberId,
teamId: team.id,
captain: index === 0,
},
});
}
}
}
async function organizations(userId: number) {
return prisma.organization.create({
data: {

View File

@@ -1,6 +1,7 @@
import { useData } from "solid-app-router";
import { createResource } from "solid-js";
import { InferQueryOutput, trpcClient } from "../../utils/trpc-client";
import type { UseDataResult } from "../../utils/types";
export default function TournamentData({
params,
@@ -20,4 +21,4 @@ export default function TournamentData({
}
export const useTournamentData = (delta?: number) =>
useData<() => InferQueryOutput<"tournament.get">>(delta);
useData<UseDataResult<InferQueryOutput<"tournament.get">>>(delta);

View File

@@ -10,7 +10,7 @@ export function InfoBanner() {
// TODO: skeleton
return (
<>
<Show when={tournament?.()} fallback={null}>
<Show when={tournament()} fallback={null}>
{(tournament) => (
<div
class={s.container}

View File

@@ -8,7 +8,7 @@ export function MapPoolTab() {
const tournament = useTournamentData(1);
return (
<Show when={tournament?.()}>
<Show when={tournament()}>
{({ mapPool }) => (
<div class={s.container}>
<div class={s.infoSquare}>

View File

@@ -0,0 +1,83 @@
import { createMemo, For, Show } from "solid-js";
import { useTournamentData } from "../TournamentPage.data";
import s from "../styles/TeamsTab.module.css";
import { AvatarWithName } from "../../../components/AvatarWithName";
import type { InferQueryOutput } from "../../../utils/trpc-client";
export function TeamsTab() {
const tournament = useTournamentData(1);
const sortCaptainFirst = (
a: { captain: boolean },
b: { captain: boolean }
) => {
return Number(b.captain) - Number(a.captain);
};
const completeTeams = createMemo(() =>
tournament()
?.teams.filter((team) => team.members.length >= 4)
.map((team) => {
return {
...team,
members: team.members.sort(sortCaptainFirst),
};
})
);
const inCompleteTeams = createMemo(() =>
tournament()
?.teams.filter((team) => team.members.length < 4)
.map((team) => {
return {
...team,
members: team.members.sort(sortCaptainFirst),
};
})
);
return (
<div class={s.container}>
<Show when={completeTeams()}>
{(teams) => <TeamsList teams={teams} title="Full teams" />}
</Show>
<Show when={inCompleteTeams()}>
{(teams) => <TeamsList teams={teams} title="Incomplete teams" />}
</Show>
</div>
);
}
function TeamsList(p: {
teams: NonNullable<InferQueryOutput<"tournament.get">>["teams"];
title: string;
}) {
if (!p.teams.length) return null;
return (
<div>
<h2 class={s.teamListTitle}>
{p.title} ({p.teams.length})
</h2>
<div class={s.teamsContainer}>
<For each={p.teams}>
{(team) => (
<>
<div class={s.teamName}>{team.name}</div>
<div class={s.membersContainer}>
<For each={team.members}>
{({ member, captain }, i) => (
<div class={s.member}>
<div class={s.orderNumber}>{captain ? "C" : i()}</div>
<AvatarWithName {...member} />
</div>
)}
</For>
</div>
</>
)}
</For>
</div>
</div>
);
}

View File

@@ -1,28 +1,36 @@
import { NavLink, Outlet } from "solid-app-router";
import { InfoBanner } from "./InfoBanner";
import s from "../styles/TournamentPage.module.css";
import { useTournamentData } from "../TournamentPage.data";
import { Show } from "solid-js";
export default function TournamentsPage() {
const tournament = useTournamentData();
return (
<div class={s.container}>
<InfoBanner />
<div style={{ "--tabs-count": 5 }} class={s.linksContainer}>
<NavLink class={s.navLink} href="overview">
Overview
</NavLink>
<NavLink class={s.navLink} href="map-pool">
Map Pool
</NavLink>
<NavLink class={s.navLink} href="bracket">
Bracket
</NavLink>
<NavLink class={s.navLink} href="teams">
Teams (23)
</NavLink>
<NavLink class={s.navLink} href="streams">
Streams (4)
</NavLink>
</div>
<Show when={tournament()}>
{(tournament) => (
<div style={{ "--tabs-count": 5 }} class={s.linksContainer}>
<NavLink class={s.navLink} href="overview">
Overview
</NavLink>
<NavLink class={s.navLink} href="map-pool">
Map Pool
</NavLink>
<NavLink class={s.navLink} href="bracket">
Bracket
</NavLink>
<NavLink class={s.navLink} href="teams">
Teams ({tournament.teams.length})
</NavLink>
<NavLink class={s.navLink} href="streams">
Streams (4)
</NavLink>
</div>
)}
</Show>
<Outlet />
</div>
);

View File

@@ -32,6 +32,26 @@ export async function findTournamentByNameForUrl({
name: true,
},
},
teams: {
select: {
checkedIn: true,
id: true,
name: true,
members: {
select: {
captain: true,
member: {
select: {
discordAvatar: true,
discordName: true,
discordId: true,
discordDiscriminator: true,
},
},
},
},
},
},
},
});

View File

@@ -0,0 +1,44 @@
.container {
display: flex;
flex-direction: column;
gap: var(--s-10);
}
.teamsContainer {
display: grid;
grid-template-columns: 1fr 1fr;
row-gap: var(--s-10);
}
.teamName {
display: flex;
align-items: center;
font-weight: var(--extra-bold);
font-size: var(--fonts-lg);
}
.membersContainer {
display: flex;
flex-direction: column;
gap: var(--s-4);
border-inline-start: 2px solid var(--theme);
padding-inline-start: var(--s-3);
}
.member {
display: flex;
align-items: center;
gap: var(--s-3);
}
.orderNumber {
color: var(--theme-secondary);
font-weight: bold;
font-size: var(--fonts-sm);
}
.teamListTitle {
margin-block-end: var(--s-8);
text-align: center;
margin-block-start: 0;
}

View File

@@ -10,7 +10,7 @@
place-items: center;
gap: var(--s-10);
grid-template-columns: repeat(2, 100px);
padding-block: var(--s-6);
padding-block: var(--s-8);
}
.navLink {

View File

@@ -0,0 +1,21 @@
.container {
display: flex;
align-items: center;
gap: var(--s-3);
font-weight: var(--semi-bold);
}
.avatar {
border-radius: var(--rounded);
block-size: 2.5rem;
inline-size: 2.5rem;
text-indent: -10000px;
filter: none;
}
.placeholder {
border-radius: var(--rounded);
block-size: 2.5rem;
inline-size: 2.5rem;
background-image: url(/svg/background-pattern.svg);
}

View File

@@ -3,8 +3,9 @@
--bg-lighter: hsl(237.3, 42.3%, 35.6%);
--text: rgba(255, 255, 255, 0.95);
--text-lighter: rgba(255, 255, 255, 0.55);
--theme: rgba(103, 65, 217);
--theme-transparent: rgba(103, 65, 217, 0.4);
--theme: hsl(255, 66.7%, 55.3%);
--theme-transparent: hsla(255, 66.7%, 55.3%, 0.4);
--theme-secondary: hsl(85, 66.7%, 55.3%);
--rounded: 16px;

1
utils/types.ts Normal file
View File

@@ -0,0 +1 @@
export type UseDataResult<Data> = () => Data;