diff --git a/Routes.tsx b/Routes.tsx index c42e58da2..05610ba8d 100644 --- a/Routes.tsx +++ b/Routes.tsx @@ -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() { > } /> <>bracket} /> - <>teams} /> + } /> <>streams} /> <>overview} /> diff --git a/components/AvatarWithName.tsx b/components/AvatarWithName.tsx new file mode 100644 index 000000000..36ce8c137 --- /dev/null +++ b/components/AvatarWithName.tsx @@ -0,0 +1,21 @@ +import s from "../styles/AvatarWithName.module.css"; + +export function AvatarWithName(p: { + discordName: string; + discordAvatar: string | null; + discordId: string; +}) { + return ( +
+
+ +
+
{p.discordName}
+
+ ); +} diff --git a/package-lock.json b/package-lock.json index 82f331817..53414e17d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8eaec05bd..280f89ef5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/prisma/migrations/20211114103710_initial/migration.sql b/prisma/migrations/20211115112609_initial/migration.sql similarity index 68% rename from prisma/migrations/20211114103710_initial/migration.sql rename to prisma/migrations/20211115112609_initial/migration.sql index 7fa818e7f..db8fe05a1 100644 --- a/prisma/migrations/20211114103710_initial/migration.sql +++ b/prisma/migrations/20211115112609_initial/migration.sql @@ -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; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index eef791104..051465b8c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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]) +} diff --git a/prisma/seed.ts b/prisma/seed.ts index bc7fcbab9..41108b0fa 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -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: { diff --git a/scenes/tournament/TournamentPage.data.ts b/scenes/tournament/TournamentPage.data.ts index aa868869b..81bb4f5bd 100644 --- a/scenes/tournament/TournamentPage.data.ts +++ b/scenes/tournament/TournamentPage.data.ts @@ -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>>(delta); diff --git a/scenes/tournament/components/InfoBanner.tsx b/scenes/tournament/components/InfoBanner.tsx index 4a1995598..f0d880455 100644 --- a/scenes/tournament/components/InfoBanner.tsx +++ b/scenes/tournament/components/InfoBanner.tsx @@ -10,7 +10,7 @@ export function InfoBanner() { // TODO: skeleton return ( <> - + {(tournament) => (
+ {({ mapPool }) => (
diff --git a/scenes/tournament/components/TeamsTab.tsx b/scenes/tournament/components/TeamsTab.tsx new file mode 100644 index 000000000..7385e02ea --- /dev/null +++ b/scenes/tournament/components/TeamsTab.tsx @@ -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 ( +
+ + {(teams) => } + + + {(teams) => } + +
+ ); +} + +function TeamsList(p: { + teams: NonNullable>["teams"]; + title: string; +}) { + if (!p.teams.length) return null; + + return ( +
+

+ {p.title} ({p.teams.length}) +

+
+ + {(team) => ( + <> +
{team.name}
+
+ + {({ member, captain }, i) => ( +
+
{captain ? "C" : i()}
+ +
+ )} +
+
+ + )} +
+
+
+ ); +} diff --git a/scenes/tournament/components/TournamentPage.tsx b/scenes/tournament/components/TournamentPage.tsx index 25c001ab9..1dfaf524f 100644 --- a/scenes/tournament/components/TournamentPage.tsx +++ b/scenes/tournament/components/TournamentPage.tsx @@ -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 (
-
- - Overview - - - Map Pool - - - Bracket - - - Teams (23) - - - Streams (4) - -
+ + {(tournament) => ( +
+ + Overview + + + Map Pool + + + Bracket + + + Teams ({tournament.teams.length}) + + + Streams (4) + +
+ )} +
); diff --git a/scenes/tournament/service.ts b/scenes/tournament/service.ts index 3d52f5c97..caa2b4e58 100644 --- a/scenes/tournament/service.ts +++ b/scenes/tournament/service.ts @@ -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, + }, + }, + }, + }, + }, + }, }, }); diff --git a/scenes/tournament/styles/TeamsTab.module.css b/scenes/tournament/styles/TeamsTab.module.css new file mode 100644 index 000000000..025ca94c9 --- /dev/null +++ b/scenes/tournament/styles/TeamsTab.module.css @@ -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; +} diff --git a/scenes/tournament/styles/TournamentPage.module.css b/scenes/tournament/styles/TournamentPage.module.css index c542a5d22..9ea31847a 100644 --- a/scenes/tournament/styles/TournamentPage.module.css +++ b/scenes/tournament/styles/TournamentPage.module.css @@ -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 { diff --git a/styles/AvatarWithName.module.css b/styles/AvatarWithName.module.css new file mode 100644 index 000000000..78bfcd7b2 --- /dev/null +++ b/styles/AvatarWithName.module.css @@ -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); +} diff --git a/styles/global.css b/styles/global.css index c5863bac0..896b08ada 100644 --- a/styles/global.css +++ b/styles/global.css @@ -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; diff --git a/utils/types.ts b/utils/types.ts new file mode 100644 index 000000000..9db8f9f7b --- /dev/null +++ b/utils/types.ts @@ -0,0 +1 @@ +export type UseDataResult = () => Data;