mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-20 01:55:26 -05:00
Add Teams Tab
This commit is contained in:
@@ -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>
|
||||
|
||||
21
components/AvatarWithName.tsx
Normal file
21
components/AvatarWithName.tsx
Normal 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
151
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
83
scenes/tournament/components/TeamsTab.tsx
Normal file
83
scenes/tournament/components/TeamsTab.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
44
scenes/tournament/styles/TeamsTab.module.css
Normal file
44
scenes/tournament/styles/TeamsTab.module.css
Normal 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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
21
styles/AvatarWithName.module.css
Normal file
21
styles/AvatarWithName.module.css
Normal 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);
|
||||
}
|
||||
@@ -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
1
utils/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type UseDataResult<Data> = () => Data;
|
||||
Reference in New Issue
Block a user