From 4cfb53a76bf832536047bcf3529565fdf6264e7a Mon Sep 17 00:00:00 2001 From: Patryk Date: Thu, 15 Aug 2024 11:04:28 +0200 Subject: [PATCH 01/37] chore: handle staff members error --- .../tournament/[tournamentId]/page.tsx | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/page.tsx index 3401ffa..9ded6ae 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/page.tsx @@ -43,9 +43,6 @@ const SingleTournament = async ({ if (getStages.status === "rejected") { throw new Error("Schedule not found"); //todo: change to proper error } - if (getStaffMembers.status === "rejected") { - throw new Error("Staff not found"); //todo: change to proper error - } let teams: TeamResponseDto[] = []; let teamSize = "1vs1"; // 1vs1 for participants @@ -54,17 +51,12 @@ const SingleTournament = async ({ ) { teamSize = `${getTournamentByAbbreviation.value?.minimumTeamSize}vs${getTournamentByAbbreviation.value?.minimumTeamSize}`; try { - const getTeamsByTournament = await TeamService.getTeamsByTournament( - params.tournamentId, - ); - teams = getTeamsByTournament; + teams = await TeamService.getTeamsByTournament(params.tournamentId); } catch (error) { throw new Error("Teams not found"); //todo: change to proper error } } - const isStaff = getStaffMembers && getStaffMembers.value.length > 0; - const mappools = await executeFetch( MappoolService.getMappoolsByTournament(params.tournamentId), ); @@ -309,35 +301,35 @@ const SingleTournament = async ({ ))} - {isStaff && ( -
-
- +
+ +

-

- Staff -

{" "} - - -
-
- {getStaffMembers.value.map((member) => ( - - ))} -
-
- )} + Staff + {" "} + + + +
+ {getStaffMembers.status === "fulfilled" + ? getStaffMembers.value.map((member) => ( + + )) + : "Staff could not be loaded"} +
+

Date: Thu, 15 Aug 2024 11:12:24 +0200 Subject: [PATCH 02/37] chore: changed disclaimer --- .../[tournamentId]/registration/page.tsx | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx index 59b0321..1ebebb3 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx @@ -66,29 +66,16 @@ const SingleTournamentRegistration = ({

Topic 1

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed - malesuada, tortor nec tincidunt tincidunt, libero nunc ullamcorper - nunc, nec interdum turpis libero a libero. Integer quis ex nec purus - lacinia ultricies. Nullam auctor, nisl vel ultricies condimentum, - purus nunc lacinia purus, nec ullamcorper libero ligula nec sapien. - Nunc id semper nunc, nec ullamcorper libero. Nullam auctor, nisl vel - ultricies condimentum, purus nunc lacinia purus, nec ullamcorper - libero ligula nec sapien. Nunc id semper nunc, nec ullamcorper - libero. -

-
-
-

Topic 2

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed - malesuada, tortor nec tincidunt tincidunt, libero nunc ullamcorper - nunc, nec interdum turpis libero a libero. Integer quis ex nec purus - lacinia ultricies. Nullam auctor, nisl vel ultricies condimentum, - purus nunc lacinia purus, nec ullamcorper libero ligula nec sapien. - Nunc id semper nunc, nec ullamcorper libero. Nullam auctor, nisl vel - ultricies condimentum, purus nunc lacinia purus, nec ullamcorper - libero ligula nec sapien. Nunc id semper nunc, nec ullamcorper - libero. + By registering for this tournament, you confirm that you meet the + eligibility requirements and agree to abide by all rules set by the + organizers. Participants are expected to behave respectfully and + fairly; any misconduct, including cheating or harassment, may result + in disqualification. You are responsible for attending matches at + the scheduled times, and failure to do so may lead to forfeiture. + Prizes, if offered, will be awarded as stated, but the organizers + reserve the right to make changes. The organizers are not liable for + any technical issues during the event. By registering, you accept + these terms and conditions.

From 7367577f55e6feb944e4cd9b88cb00d171bd57a4 Mon Sep 17 00:00:00 2001 From: Patryk Date: Thu, 15 Aug 2024 11:25:12 +0200 Subject: [PATCH 03/37] chore: updated socials links --- src/ui/organisms/Socials/Socials.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/organisms/Socials/Socials.tsx b/src/ui/organisms/Socials/Socials.tsx index 83366b7..96f9f17 100644 --- a/src/ui/organisms/Socials/Socials.tsx +++ b/src/ui/organisms/Socials/Socials.tsx @@ -10,28 +10,28 @@ export const Socials = () => { , }} /> , }} /> , }} /> , }} /> From c9e8f8b7320a8a60d6c228f4b03cadde94bd6af9 Mon Sep 17 00:00:00 2001 From: Patryk Date: Thu, 15 Aug 2024 11:25:53 +0200 Subject: [PATCH 04/37] chore: updated disclaimer --- .../tournament/[tournamentId]/registration/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx index 1ebebb3..c0ec038 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/registration/page.tsx @@ -64,7 +64,7 @@ const SingleTournamentRegistration = ({ } >
-

Topic 1

+

Disclaimer

By registering for this tournament, you confirm that you meet the eligibility requirements and agree to abide by all rules set by the From 2ea198127ba7bb6f162c03a005f20e6a4eb42e5a Mon Sep 17 00:00:00 2001 From: Patryk Date: Thu, 15 Aug 2024 18:17:55 +0200 Subject: [PATCH 05/37] chore: added teams page --- .../[tournamentAbbreviation]/teams/page.tsx | 148 ++++++++++++++++++ .../[tournamentId]/teams/[teamId]/page.tsx | 12 +- 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx new file mode 100644 index 0000000..50cd24e --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import Image from "next/image"; +import { AdminTeamService } from "../../../../../../../generated"; +import { executeFetch } from "@/lib/executeFetch"; + +const TeamsPage = async ({ + params: { tournamentAbbreviation }, +}: { + params: { + tournamentAbbreviation: string; + }; +}) => { + const teams = await executeFetch(AdminTeamService.getTeams(tournamentAbbreviation)); + if (!teams.status) { + return

Failed to fetch teams
; + } + return ( +
+

Teams

+
+ + {/* head */} + + + + + + + + + + + {teams.response.map((team) => { + return ( + + + + + + + + ); + })} + +
Team nameCaptainRosterStatusActions
+
+
+
+ Team logo +
+
+ {team.name} +
+
+
+
+
+ {team.captain.user.username} +
+
+ {team.captain.user.username} +
+
+ {team.participants.map((participant) => ( +
+
+
+
+ {participant.user.username} +
+
+ {participant.user.username} +
+
+ ))} +
{team.status} +
{ + "use server"; + await executeFetch( + AdminTeamService.changeTeamStatus( + tournamentAbbreviation, + team.id, + "ACCEPTED", + ), + [ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${team.id}`, + `/account/`, + ], + ); + }} + > + +
+
{ + "use server"; + await executeFetch( + AdminTeamService.changeTeamStatus( + tournamentAbbreviation, + team.id, + "REJECTED", + ), + [ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${team.id}`, + `/account/`, + ], + ); + }} + > + +
+
+
+
+ ); +}; + +export default TeamsPage; diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx index 620bfbe..9a0d1f5 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx @@ -39,7 +39,10 @@ const TeamPage = async ({
)} -

{getTeam.response.name}

+
+

{getTeam.response.name}

+

Status: {getTeam.response.status}

+
{isCaptain && (
{isCaptain && ( <> +
+

+ Once you finish adding your team roster and setting team image, ping + Host of the tournament to change your team status to ACCEPTED. Any + changes in the team will change its status to PENDING. +

+
Date: Fri, 16 Aug 2024 11:06:07 +0200 Subject: [PATCH 06/37] feat: added heyapi as main fetch request --- openapi-ts.config.ts | 13 + package-lock.json | 796 ++++++++++++++---- package.json | 6 +- src/actions/admin/adminBeatMapActions.ts | 55 +- .../admin/adminQualificationRoomsActions.ts | 95 ++- src/actions/admin/adminStaffMembersActions.ts | 129 +-- src/actions/admin/adminStageActions.ts | 95 ++- src/actions/admin/editTournamentAction.ts | 60 +- .../admin/editTournamentImageAction.ts | 57 +- src/actions/public/getUserAction.ts | 9 +- .../(withAuth)/account/create-team/page.tsx | 49 -- .../dashboard/CreateTournamentModal.tsx | 18 +- .../[tournamentAbbreviation]/layout.tsx | 18 +- .../[stageType]/[mappoolId]/AddBeatMap.tsx | 20 +- .../mappool/[stageType]/[mappoolId]/page.tsx | 71 +- .../qualification-rooms/page.tsx | 187 ++-- .../settings/page.tsx | 12 +- .../staff-members/page.tsx | 77 +- .../stages/StageForm.tsx | 48 +- .../[tournamentAbbreviation]/stages/page.tsx | 68 +- .../[tournamentAbbreviation]/teams/page.tsx | 70 +- .../(main-page)/(withAuth)/dashboard/page.tsx | 10 +- .../tournament/[tournamentId]/layout.tsx | 34 +- .../mappool/[stageType]/page.tsx | 21 +- .../tournament/[tournamentId]/page.tsx | 186 ++-- .../[tournamentId]/registration/layout.tsx | 2 +- .../tournament/[tournamentId]/rules/page.tsx | 72 +- .../[tournamentId]/schedule/page.tsx | 10 +- .../tournament/[tournamentId]/staff/page.tsx | 14 +- src/lib/executeFetch.ts | 51 -- src/lib/helpers.ts | 33 +- src/lib/restClient.ts | 5 + src/middleware.ts | 11 + src/ui/molecules/LoginAvatar/LoginAvatar.tsx | 9 +- src/ui/organisms/Footer/Footer.tsx | 6 +- .../organisms/ScheduleList/ScheduleList.tsx | 2 +- .../TournamentList/TournamentList.tsx | 20 +- 37 files changed, 1458 insertions(+), 981 deletions(-) create mode 100644 openapi-ts.config.ts delete mode 100644 src/app/(main-page)/(withAuth)/account/create-team/page.tsx delete mode 100644 src/lib/executeFetch.ts create mode 100644 src/lib/restClient.ts diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts new file mode 100644 index 0000000..4730b46 --- /dev/null +++ b/openapi-ts.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@hey-api/openapi-ts"; + +export default defineConfig({ + client: "@hey-api/client-fetch", + input: "./openapi.json", + output: { + format: "prettier", + path: "./client", + }, + types: { + enums: "typescript", + }, +}); diff --git a/package-lock.json b/package-lock.json index 416d8a7..292cea0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "aimcup-frontend", "version": "0.1.0", "dependencies": { + "@hey-api/client-fetch": "^0.2.4", "@next/font": "^14.2.4", "@reduxjs/toolkit": "^2.2.5", "@types/node": "20.6.0", @@ -34,6 +35,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@hey-api/openapi-ts": "^0.52.8", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "autoprefixer": "^10.4.16", @@ -43,7 +45,6 @@ "eslint-plugin-import": "^2.29.1", "husky": "^8.0.3", "lint-staged": "^15.2.0", - "openapi-typescript-codegen": "^0.29.0", "postcss": "^8.4.32", "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.5.9", @@ -71,10 +72,11 @@ } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.6.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.2.tgz", - "integrity": "sha512-ENUdLLT04aDbbHCRwfKf8gR67AhV0CdFrOAtk+FcakBAgaq6ds3HLK9X0BCyiFUz8pK9uP+k6YZyJaGG7Mt7vQ==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz", + "integrity": "sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog==", "dev": true, + "license": "MIT", "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", @@ -212,6 +214,44 @@ "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==", "license": "MIT" }, + "node_modules/@hey-api/client-fetch": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.2.4.tgz", + "integrity": "sha512-SGTVAVw3PlKDLw+IyhNhb/jCH3P1P2xJzLxA8Kyz1g95HrkYOJdRpl9F5I7LLwo9aCIB7nwR2NrSeX7QaQD7vQ==", + "license": "MIT" + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.52.8", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.52.8.tgz", + "integrity": "sha512-OKSHz3083Qnxn+RyPwRzCFjmeN3ZPmSw8aEWwsKyeVU1vik4H1MBfuFDiIP7luf929AYEN3nLA2mrr9wVRENnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "11.7.0", + "c12": "1.11.1", + "commander": "12.1.0", + "handlebars": "4.7.8" + }, + "bin": { + "openapi-ts": "bin/index.cjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/@hey-api/openapi-ts/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -815,7 +855,8 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@next/env": { "version": "14.2.4", @@ -1360,9 +1401,10 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1756,6 +1798,35 @@ "node": ">=10.16.0" } }, + "node_modules/c12": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-1.11.1.tgz", + "integrity": "sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^1.21.6", + "mlly": "^1.7.1", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.1.1", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.4" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -1783,18 +1854,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1840,16 +1899,11 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1862,6 +1916,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -1878,6 +1935,26 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -1990,6 +2067,23 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -2166,6 +2260,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2174,6 +2275,13 @@ "node": ">=6" } }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2216,6 +2324,19 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3015,18 +3136,30 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">=14.14" + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/fs.realpath": { @@ -3150,6 +3283,26 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -3253,6 +3406,7 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -3832,10 +3986,11 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -3891,18 +4046,6 @@ "json5": "lib/cli.js" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4239,6 +4382,59 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4281,7 +4477,8 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/next": { "version": "14.2.4", @@ -4377,6 +4574,13 @@ "react-dom": ">= 16.0.0" } }, + "node_modules/node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -4433,6 +4637,27 @@ "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" }, + "node_modules/nypm": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz", + "integrity": "sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4567,6 +4792,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", + "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", + "dev": true, + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4590,31 +4822,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-typescript-codegen": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.29.0.tgz", - "integrity": "sha512-/wC42PkD0LGjDTEULa/XiWQbv4E9NwLjwLjsaJ/62yOsoYhwvmBR31kPttn1DzQ2OlGe5stACcF/EIkZk43M6w==", - "dev": true, - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.5.4", - "camelcase": "^6.3.0", - "commander": "^12.0.0", - "fs-extra": "^11.2.0", - "handlebars": "^4.7.8" - }, - "bin": { - "openapi": "bin/index.js" - } - }, - "node_modules/openapi-typescript-codegen/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4729,6 +4936,20 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -4775,6 +4996,18 @@ "node": ">= 6" } }, + "node_modules/pkg-types": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", + "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } + }, "node_modules/postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -5084,6 +5317,17 @@ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==", "license": "MIT" }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -5636,6 +5880,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -6009,6 +6254,34 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6174,11 +6447,19 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", + "integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==", "dev": true, + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -6201,15 +6482,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -6344,7 +6616,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "9.0.0", @@ -6454,6 +6727,13 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", @@ -6496,9 +6776,9 @@ "dev": true }, "@apidevtools/json-schema-ref-parser": { - "version": "11.6.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.2.tgz", - "integrity": "sha512-ENUdLLT04aDbbHCRwfKf8gR67AhV0CdFrOAtk+FcakBAgaq6ds3HLK9X0BCyiFUz8pK9uP+k6YZyJaGG7Mt7vQ==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz", + "integrity": "sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog==", "dev": true, "requires": { "@jsdevtools/ono": "^7.1.3", @@ -6597,6 +6877,31 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" }, + "@hey-api/client-fetch": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.2.4.tgz", + "integrity": "sha512-SGTVAVw3PlKDLw+IyhNhb/jCH3P1P2xJzLxA8Kyz1g95HrkYOJdRpl9F5I7LLwo9aCIB7nwR2NrSeX7QaQD7vQ==" + }, + "@hey-api/openapi-ts": { + "version": "0.52.8", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.52.8.tgz", + "integrity": "sha512-OKSHz3083Qnxn+RyPwRzCFjmeN3ZPmSw8aEWwsKyeVU1vik4H1MBfuFDiIP7luf929AYEN3nLA2mrr9wVRENnw==", + "dev": true, + "requires": { + "@apidevtools/json-schema-ref-parser": "11.7.0", + "c12": "1.11.1", + "commander": "12.1.0", + "handlebars": "4.7.8" + }, + "dependencies": { + "commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true + } + } + }, "@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -7171,9 +7476,9 @@ } }, "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==" }, "acorn-jsx": { "version": "5.3.2", @@ -7436,6 +7741,26 @@ "streamsearch": "^1.1.0" } }, + "c12": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-1.11.1.tgz", + "integrity": "sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==", + "dev": true, + "requires": { + "chokidar": "^3.6.0", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^1.21.6", + "mlly": "^1.7.1", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.1.1", + "rc9": "^2.1.2" + } + }, "call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -7453,12 +7778,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, "camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -7480,9 +7799,9 @@ } }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -7506,6 +7825,21 @@ } } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "requires": { + "consola": "^3.2.3" + } + }, "cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -7588,6 +7922,18 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, + "consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -7702,11 +8048,23 @@ "object-keys": "^1.1.1" } }, + "defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true + }, "dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, + "destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true + }, "detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -7740,6 +8098,12 @@ "esutils": "^2.0.2" } }, + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -8351,15 +8715,24 @@ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true }, - "fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } } }, "fs.realpath": { @@ -8436,6 +8809,22 @@ "resolve-pkg-maps": "^1.0.0" } }, + "giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "requires": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + } + }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -8876,9 +9265,9 @@ } }, "jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true }, "jose": { @@ -8922,16 +9311,6 @@ "minimist": "^1.2.0" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -9167,6 +9546,45 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "requires": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -9243,6 +9661,12 @@ "prop-types": "^15.8.1" } }, + "node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true + }, "node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -9283,6 +9707,20 @@ "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" }, + "nypm": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz", + "integrity": "sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==", + "dev": true, + "requires": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9374,6 +9812,12 @@ "es-abstract": "^1.22.1" } }, + "ohash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", + "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9391,27 +9835,6 @@ "mimic-fn": "^4.0.0" } }, - "openapi-typescript-codegen": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/openapi-typescript-codegen/-/openapi-typescript-codegen-0.29.0.tgz", - "integrity": "sha512-/wC42PkD0LGjDTEULa/XiWQbv4E9NwLjwLjsaJ/62yOsoYhwvmBR31kPttn1DzQ2OlGe5stACcF/EIkZk43M6w==", - "dev": true, - "requires": { - "@apidevtools/json-schema-ref-parser": "^11.5.4", - "camelcase": "^6.3.0", - "commander": "^12.0.0", - "fs-extra": "^11.2.0", - "handlebars": "^4.7.8" - }, - "dependencies": { - "commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true - } - } - }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -9488,6 +9911,18 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9516,6 +9951,17 @@ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true }, + "pkg-types": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", + "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", + "dev": true, + "requires": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } + }, "postcss": { "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", @@ -9658,6 +10104,16 @@ "fast-diff": "1.1.2" } }, + "rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "requires": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -10283,6 +10739,28 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, + "tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10403,10 +10881,16 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" }, + "ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true + }, "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", + "integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==", "dev": true, "optional": true }, @@ -10421,12 +10905,6 @@ "which-boxed-primitive": "^1.0.2" } }, - "universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true - }, "update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -10590,6 +11068,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yaml": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", diff --git a/package.json b/package.json index 1414b42..f8e3ff0 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,11 @@ "prepare": "husky install", "open:api": "curl https://api.aimcup.xyz/v3/api-docs -o openapi.json", "open:api:local": "curl http://localhost:8080/v3/api-docs -o openapi.json", - "open:generate": "openapi --input ./openapi.json --output ./generated" + "open:generate": "openapi --input ./openapi.json --output ./generated", + "openapi-ts": "openapi-ts" }, "dependencies": { + "@hey-api/client-fetch": "^0.2.4", "@next/font": "^14.2.4", "@reduxjs/toolkit": "^2.2.5", "@types/node": "20.6.0", @@ -41,6 +43,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@hey-api/openapi-ts": "^0.52.8", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "autoprefixer": "^10.4.16", @@ -50,7 +53,6 @@ "eslint-plugin-import": "^2.29.1", "husky": "^8.0.3", "lint-staged": "^15.2.0", - "openapi-typescript-codegen": "^0.29.0", "postcss": "^8.4.32", "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.5.9", diff --git a/src/actions/admin/adminBeatMapActions.ts b/src/actions/admin/adminBeatMapActions.ts index 9dd3894..3141838 100644 --- a/src/actions/admin/adminBeatMapActions.ts +++ b/src/actions/admin/adminBeatMapActions.ts @@ -1,33 +1,34 @@ "use server"; - -import { - AdminMappoolService, - type BeatmapModificationResponseDto, - type BeatmapResponseDto, -} from "../../../generated"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; +import { addBeatmap, type modification } from "../../../client"; import { type AddBeatMapSchemaType } from "@/formSchemas/addBeatMapSchema"; -export async function addBeatMapAction(data: AddBeatMapSchemaType) { +export async function addBeatMapAction(formData: AddBeatMapSchemaType) { "use server"; - return executeFetch( - AdminMappoolService.addBeatmap( - data.tournamentAbb, - data.mappoolId, - data.modification as BeatmapModificationResponseDto.modification, - { - url: data.url, - position: +data.position, - isCustom: data?.isCustom === "on", - }, - ), - ["/", "/dashboard/[tournamentAbb]/mappool/[stageType]/[mappoolId]"], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await addBeatmap({ + path: { + abbreviation: formData.tournamentAbb, + modification: formData.modification as modification, + mappoolId: formData.mappoolId, + }, + body: { + url: formData.url, + position: +formData.position, + isCustom: formData?.isCustom === "on", + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + // todo: ADD REVALIDATE_PATHS + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/admin/adminQualificationRoomsActions.ts b/src/actions/admin/adminQualificationRoomsActions.ts index 442c704..58a2f1d 100644 --- a/src/actions/admin/adminQualificationRoomsActions.ts +++ b/src/actions/admin/adminQualificationRoomsActions.ts @@ -1,48 +1,71 @@ "use server"; -import { AdminQualificationService, type AdminStaffMemberService } from "../../../generated"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; import { type CreateQualificationRoomsSchemaType } from "@/formSchemas/createQualificationRoomsSchema"; import { type EditQualificationRoomsSchemaType } from "@/formSchemas/editQualificationRoomSchema"; +import { createQualificationRooms, updateQualificationRoom } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/helpers"; -export async function createQualificationRoomsAction(data: CreateQualificationRoomsSchemaType) { +export async function createQualificationRoomsAction(formData: CreateQualificationRoomsSchemaType) { "use server"; - return executeFetch( - AdminQualificationService.createQualificationRooms(data.tournamentAbbreviation, { - amount: +data.amount, - timeOffset: +data.offset, - startDateTime: data.dataTimeStart, - }), - ["/", "/dashboard/[tournamentAbb]/qualification-rooms"], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await createQualificationRooms({ + path: { + abbreviation: formData.tournamentAbbreviation, + }, + body: { + amount: +formData.amount, + timeOffset: +formData.offset, + startDateTime: formData.dataTimeStart, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/qualification-rooms`, + ]); + + return { + status: true as const, + response: data, + }; } -export async function editQualificationRoomsAction(data: EditQualificationRoomsSchemaType) { +export async function editQualificationRoomsAction(formData: EditQualificationRoomsSchemaType) { "use server"; - return executeFetch( - AdminQualificationService.updateQualificationRoom( - data.tournamentAbbreviation, - data.roomId, - { - rosterIds: data.rosterIds, - staffMemberId: data.staffMemberId, - startDate: data.dataTimeStart, - }, - ), - ["/", `/dashboard/${data.tournamentAbbreviation}/qualification-rooms`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await updateQualificationRoom({ + path: { + abbreviation: formData.tournamentAbbreviation, + roomId: formData.roomId, + }, + body: { + rosterIds: formData.rosterIds?.map((roster) => roster), + staffMemberId: formData.staffMemberId, + startDate: formData.dataTimeStart, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/qualification-rooms`, + ]); + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/admin/adminStaffMembersActions.ts b/src/actions/admin/adminStaffMembersActions.ts index 05b4dea..ae59a7e 100644 --- a/src/actions/admin/adminStaffMembersActions.ts +++ b/src/actions/admin/adminStaffMembersActions.ts @@ -1,65 +1,96 @@ "use server"; -import { AdminStaffMemberService } from "../../../generated"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; import { type AddStaffMembersSchemaType } from "@/formSchemas/addEditStaffMembersSchema"; import { type AddUserLessStaffMembersSchemaType } from "@/formSchemas/addEditUserLessStaffMembersSchema"; +import { addStaffMembers, updateStaffMembers } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/helpers"; -export async function editStaffMemberAction(data: AddStaffMembersSchemaType) { +export async function editStaffMemberAction(formData: AddStaffMembersSchemaType) { "use server"; - return executeFetch( - AdminStaffMemberService.updateStaffMembers(data.tournamentAbbreviation, data.osuId, { - discordId: data.discordId, - roles: data.roles, - permissions: data.permissions, - }), - ["/", `/dashboard/${data.tournamentAbbreviation}/staff-members`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await updateStaffMembers({ + path: { + staffMemberId: formData.osuId, + abbreviation: formData.tournamentAbbreviation, + }, + body: { + discordId: formData.discordId, + roles: formData.roles, + permissions: formData.permissions, + }, + }); + + if (error) { + return { + status: false as const, + // errorMessage: error.errors?.map((e) => e).join(", "), //todo + }; + } + + multipleRevalidatePaths(["/", `/dashboard/${formData.tournamentAbbreviation}/staff-members`]); + + return { + status: true as const, + response: data, + }; } -export async function addStaffMemberAction(data: AddStaffMembersSchemaType) { +export async function addStaffMemberAction(formData: AddStaffMembersSchemaType) { "use server"; - return executeFetch( - AdminStaffMemberService.addStaffMembers(data.tournamentAbbreviation, { - osuId: data.osuId, - discordId: data.discordId, - roles: data.roles || [], - permissions: data.permissions || [], - }), - ["/", `/dashboard/${data.tournamentAbbreviation}/staff-members`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await addStaffMembers({ + path: { + abbreviation: formData.tournamentAbbreviation, + }, + body: { + osuId: formData.osuId, + discordId: formData.discordId, + roles: formData.roles, + permissions: formData.permissions, + }, + }); + + if (error) { + return { + status: false as const, + // errorMessage: error.errors?.map((e) => e).join(", "), //todo + }; + } + + multipleRevalidatePaths(["/", `/dashboard/${formData.tournamentAbbreviation}/staff-members`]); + + return { + status: true as const, + response: data, + }; } -export async function addUserLessStaffMemberAction(data: AddUserLessStaffMembersSchemaType) { +export async function addUserLessStaffMemberAction(formData: AddUserLessStaffMembersSchemaType) { "use server"; - return executeFetch( - AdminStaffMemberService.addStaffMembers(data.tournamentAbbreviation, { - roles: data.roles || [], - username: data.username, - redirectUrl: data.redirectUrl, - imageUrl: data.imageUrl, - }), - ["/", `/dashboard/${data.tournamentAbbreviation}/staff-members`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await addStaffMembers({ + path: { + abbreviation: formData.tournamentAbbreviation, + }, + body: { + username: formData.username, + roles: formData.roles, + redirectUrl: formData.redirectUrl, + imageUrl: formData.imageUrl, + }, + }); + + if (error) { + return { + status: false as const, + // errorMessage: error.errors?.map((e) => e).join(", "), //todo + }; + } + + multipleRevalidatePaths(["/", `/dashboard/${formData.tournamentAbbreviation}/staff-members`]); + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/admin/adminStageActions.ts b/src/actions/admin/adminStageActions.ts index 8646813..e9b8489 100644 --- a/src/actions/admin/adminStageActions.ts +++ b/src/actions/admin/adminStageActions.ts @@ -1,48 +1,73 @@ "use server"; -import { AdminStageService, type StageResponseDto } from "../../../generated"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; import { type CreateStageSchemaType, type EditStageSchemaType, } from "@/formSchemas/createStageSchema"; +import { createStage, stageType, updateStage } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/helpers"; -export async function createStageAction(data: CreateStageSchemaType) { +export async function createStageAction(formData: CreateStageSchemaType) { "use server"; - return executeFetch( - AdminStageService.createStage(data.tournamentAbb, { - endDate: new Date(data.endDate).toISOString(), - stageType: data.stageType as StageResponseDto.stageType, - startDate: new Date(data.startDate).toISOString(), - }), - ["/", `/dashboard/${data.tournamentAbb}`, `/tournament/${data.tournamentAbb}`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await createStage({ + path: { + abbreviation: formData.tournamentAbb, + }, + body: { + startDate: new Date(formData.startDate).toISOString(), + endDate: new Date(formData.endDate).toISOString(), + stageType: formData.stageType as stageType, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbb}`, + `/tournament/${formData.tournamentAbb}`, + ]); + + return { + status: true as const, + response: data, + }; } -export async function editStageAction(data: EditStageSchemaType) { +export async function editStageAction(formData: EditStageSchemaType) { "use server"; - return executeFetch( - AdminStageService.updateStage( - data.tournamentAbb, - data.stageType as StageResponseDto.stageType, - { - endDate: new Date(data.endDate).toISOString(), - startDate: new Date(data.startDate).toISOString(), - }, - ), - ["/", `/dashboard/${data.tournamentAbb}`, `/tournament/${data.tournamentAbb}`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + const { data, error } = await updateStage({ + path: { + abbreviation: formData.tournamentAbb, + stageType: formData.stageType as stageType, + }, + body: { + startDate: new Date(formData.startDate).toISOString(), + endDate: new Date(formData.endDate).toISOString(), + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbb}`, + `/tournament/${formData.tournamentAbb}`, + ]); + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/admin/editTournamentAction.ts b/src/actions/admin/editTournamentAction.ts index 732bb32..69123a1 100644 --- a/src/actions/admin/editTournamentAction.ts +++ b/src/actions/admin/editTournamentAction.ts @@ -1,34 +1,44 @@ "use server"; -import { AdminTournamentService, type UpdateTournamentDataRequestDto } from "../../../generated"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; import { type EditTournamentSchemaType } from "@/formSchemas/editTournamentSchema"; +import { updateTournament } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/helpers"; -export async function editTournamentAction(data: EditTournamentSchemaType, rules: string) { +export async function editTournamentAction(formData: EditTournamentSchemaType, formRules: string) { "use server"; - return executeFetch( - AdminTournamentService.updateTournament(data.oldAbbreviation, { + const { data, error } = await updateTournament({ + path: { + abbreviation: formData.oldAbbreviation, + }, + body: { + rules: formRules, + name: formData.name, + abbreviation: formData.abbreviation, prizePool: [ - { type: 0, prize: data.prize0 }, - { type: 1, prize: data.prize1 }, - { type: 2, prize: data.prize2 }, + { type: 0, prize: formData.prize0 }, + { type: 1, prize: formData.prize1 }, + { type: 2, prize: formData.prize2 }, ], - abbreviation: data.abbreviation, - name: data.name, - rules: rules, - }), - [ - "/", - `/dashboard/${data.abbreviation}`, - `/dashboard/${data.abbreviation}/settings`, - `/tournament/${data.abbreviation}`, - ], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + multipleRevalidatePaths([ + "/", + `/dashboard/${formData.abbreviation}`, + `/dashboard/${formData.abbreviation}/settings`, + `/tournament/${formData.abbreviation}`, + ]); + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/admin/editTournamentImageAction.ts b/src/actions/admin/editTournamentImageAction.ts index 58492c2..4fad62a 100644 --- a/src/actions/admin/editTournamentImageAction.ts +++ b/src/actions/admin/editTournamentImageAction.ts @@ -1,34 +1,53 @@ "use server"; -import { AdminTournamentService } from "../../../generated"; -import { executeFetch } from "@/lib/executeFetch"; +import { updateTournamentImage } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/helpers"; export async function editTournamentImageAction(formData: FormData) { "use server"; const image = formData.get("image"); if (!image) { - throw new Error("Image is required"); + return { + status: false as const, + errorMessage: "Image is required", + }; } const abbreviation = formData.get("abbreviation"); if (!abbreviation) { - throw new Error("Abbreviation is required"); + return { + status: false as const, + errorMessage: "Abbreviation is required", + }; } - return executeFetch( - AdminTournamentService.updateTournamentImage(abbreviation as string, "BANNER", { + const { data, error } = await updateTournamentImage({ + path: { + abbreviation: abbreviation as string, + }, + query: { + imageType: "BANNER", + }, + body: { image: image as Blob, - }), - ["/", `/dashboard/${abbreviation as string}`, `/tournament/${abbreviation as string}`], - ) - .then((_res) => { - return { - status: true as const, - }; - }) - .catch((_error) => { - return { - status: false as const, - }; - }); + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + multipleRevalidatePaths([ + "/", + `/dashboard/${abbreviation as string}`, + `/tournament/${abbreviation as string}`, + ]); + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/public/getUserAction.ts b/src/actions/public/getUserAction.ts index 978bff0..f10ad45 100644 --- a/src/actions/public/getUserAction.ts +++ b/src/actions/public/getUserAction.ts @@ -1,15 +1,12 @@ import { cache } from "react"; -import { OpenAPI, UserService } from "../../../generated"; +import { me } from "../../../client"; import { verifySession } from "@/lib/session"; export const getUser = cache(async () => { const JWT = await verifySession(); if (typeof JWT.token === "string" && JWT.isAuth) { - OpenAPI.HEADERS = { - Cookie: `token=${JWT.token}`, - }; - const userData = await UserService.me(); - return userData; + const { data } = await me(); + return data; } return null; }); diff --git a/src/app/(main-page)/(withAuth)/account/create-team/page.tsx b/src/app/(main-page)/(withAuth)/account/create-team/page.tsx deleted file mode 100644 index 7b1c2db..0000000 --- a/src/app/(main-page)/(withAuth)/account/create-team/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Button } from "@ui/atoms/Button/Button"; - -const CreateTeamPage = async ({ - params, -}: { - params: { - tournamentId: string; - }; -}) => { - return ( - <> - -
-
-
-

- Create a team -

-

- Enter your team name -

- - -
-
- -
- - - ); -}; - -export default CreateTeamPage; diff --git a/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx index 09ef3ef..bdb7ee8 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx @@ -4,7 +4,6 @@ import React, { useRef } from "react"; import { IoMdAdd } from "react-icons/io"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; -import { TournamentRequestDto } from "../../../../../generated"; import { Button } from "@ui/atoms/Button/Button"; import Modal from "@ui/organisms/Modal/Modal"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; @@ -12,6 +11,7 @@ import { createTournamentSchema } from "@/formSchemas/createTournamentSchema"; import { Input } from "@ui/atoms/Forms/Input/Input"; import { ComboBox, type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; import { createTournamentAction } from "@/actions/public/createTournamentAction"; +import { qualificationType, tournamentType } from '../../../../../client' const CreateTournamentModal = () => { const modalRef = useRef(null); @@ -30,20 +30,20 @@ const CreateTournamentModal = () => { router.push(`/dashboard/${tournamentResponseDto.response.abbreviation}`); }); const [tournamentTourType, setTournamentTourType] = - React.useState( - TournamentRequestDto.tournamentType.TEAM_VS, + React.useState( + tournamentType.TEAM_VS, ); const tournamentTourTypeSelectOptions: selectOptions[] = [ - { id: TournamentRequestDto.tournamentType.TEAM_VS, label: "Team vs" }, - { id: TournamentRequestDto.tournamentType.INTERNATIONAL, label: "International" }, + { id: tournamentType.TEAM_VS, label: "Team vs" }, + { id: tournamentType.INTERNATIONAL, label: "International" }, // todo // { id: TournamentRequestDto.tournamentType.PARTICIPANT_VS, label: "Participant vs" }, ]; const qualificationTypeSelectOptions: selectOptions[] = [ - { id: TournamentRequestDto.qualificationType.Z_SUM, label: "Z-sum" }, - { id: TournamentRequestDto.qualificationType.ZIP_LAW, label: "Zip law" }, + { id: qualificationType.Z_SUM, label: "Z-sum" }, + { id: qualificationType.ZIP_LAW, label: "Zip law" }, ]; return ( @@ -86,7 +86,7 @@ const CreateTournamentModal = () => { selectOptions={tournamentTourTypeSelectOptions} onSelect={(e) => { setTournamentTourType( - e.target.value as TournamentRequestDto.tournamentType, + e.target.value as tournamentType, ); }} /> @@ -114,7 +114,7 @@ const CreateTournamentModal = () => { label={"Maximum rank limit"} /> {tournamentTourType !== - TournamentRequestDto.tournamentType.PARTICIPANT_VS && ( + tournamentType.PARTICIPANT_VS && ( <> {tournamentData.errorMessage}; - } + const { data:tournamentData } = await getTournamentByAbbreviation({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); return ( <> @@ -48,7 +44,7 @@ export default async function Layout({
  • Matches
  • - {tournamentData?.response.tournamentType !== tournamentType.PARTICIPANT_VS ? ( + {tournamentData?.tournamentType !== tournamentType.PARTICIPANT_VS ? (
  • Teams
  • diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx index 97089aa..e81a388 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useRef } from "react"; import { toast } from "sonner"; -import { BeatmapModificationResponseDto } from "../../../../../../../../../generated"; +import { modification } from "../../../../../../../../../client"; import { Button } from "@ui/atoms/Button/Button"; import Modal from "@ui/organisms/Modal/Modal"; import { ComboBox } from "@ui/atoms/Forms/Select/ComboBox"; @@ -37,39 +37,39 @@ export const AddBeatMap = ({ tournamentAbb, mappoolId }: IAddStageFormProps) => const modificationTypeSelectOptions = [ { - id: BeatmapModificationResponseDto.modification.DT, + id: modification.DT, label: "DT", }, { - id: BeatmapModificationResponseDto.modification.HR, + id: modification.HR, label: "HR", }, { - id: BeatmapModificationResponseDto.modification.HD, + id: modification.HD, label: "HD", }, { - id: BeatmapModificationResponseDto.modification.FL, + id: modification.FL, label: "FL", }, { - id: BeatmapModificationResponseDto.modification.EZ, + id: modification.EZ, label: "EZ", }, { - id: BeatmapModificationResponseDto.modification.NM, + id: modification.NM, label: "NM", }, { - id: BeatmapModificationResponseDto.modification.TB, + id: modification.TB, label: "TB", }, { - id: BeatmapModificationResponseDto.modification.FM, + id: modification.FM, label: "FM", }, { - id: BeatmapModificationResponseDto.modification.HT, + id: modification.HT, label: "HT", }, ]; diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx index f6f6bca..f6e5b65 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx @@ -1,26 +1,33 @@ import React from "react"; import Image from "next/image"; -import { AdminMappoolService, type StageResponseDto } from "../../../../../../../../../generated"; -import { stageTypeEnumToString } from "@/lib/helpers"; +import { multipleRevalidatePaths, stageTypeEnumToString } from "@/lib/helpers"; import { Button } from "@ui/atoms/Button/Button"; import { AddBeatMap } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap"; -import { executeFetch } from "@/lib/executeFetch"; +import { + deleteBeatmap, + getMappool, + releaseMappool, + stageType, +} from "../../../../../../../../../client"; const StageTypePage = async ({ params: { tournamentAbbreviation, stageType, mappoolId }, }: { params: { tournamentAbbreviation: string; - stageType: StageResponseDto.stageType; + stageType: stageType; mappoolId: string; }; }) => { - const getMappoolData = await executeFetch( - AdminMappoolService.getMappool(tournamentAbbreviation, stageType), - ); + const { data: getMappoolData, error } = await getMappool({ + path: { + stageType, + abbreviation: tournamentAbbreviation, + }, + }); - if (!getMappoolData.status) { - return
    {getMappoolData.errorMessage}
    ; + if (error) { + return
    {error.errors?.map((e) => e)}
    ; } return ( @@ -32,18 +39,24 @@ const StageTypePage = async ({ className={"mb-3"} action={async (_e) => { "use server"; - await executeFetch( - AdminMappoolService.releaseMappool( - tournamentAbbreviation, + await releaseMappool({ + path: { stageType, - !getMappoolData.response.isReleased, - ), - ["/", "/dashboard/[tournamentAbb]/mappool/[stageType]/[mappoolId]"], - ); + abbreviation: tournamentAbbreviation, + }, + query: { + release: !getMappoolData.isReleased, + }, + }); + + multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/mappool/${stageType}/${mappoolId}`, + ]); }} > @@ -69,7 +82,7 @@ const StageTypePage = async ({ - {getMappoolData.response.beatmapsModifications.map( + {getMappoolData.beatmapsModifications.map( (mappool) => mappool.beatmaps?.map((beatmap) => ( @@ -111,18 +124,18 @@ const StageTypePage = async ({
    { "use server"; - await executeFetch( - AdminMappoolService.deleteBeatmap( - tournamentAbbreviation, + await deleteBeatmap({ + path: { + abbreviation: tournamentAbbreviation, mappoolId, - mappool.modification, - beatmap.id, - ), - [ - "/", - "/dashboard/[tournamentAbb]/mappool/[stageType]/[mappoolId]", - ], - ); + modification: mappool.modification, + beatmapId: beatmap.id, + }, + }); + multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/mappool/${stageType}/${mappoolId}`, + ]); }} >
    ; - } - if (!getStagesData.status) { -
    {getStagesData.errorMessage}
    ; - } + const { data:tournamentData } = await getTournamentByAbbreviation({ + path: { + abbreviation:params.tournamentId + } + }); const getStateTypes = - getStagesData.status && - getStagesData.response.filter((stage) => !!stage.mappool).map((stage) => stage.stageType); + getStagesData && + getStagesData.filter((stage) => !!stage.mappool).map((stage) => stage.stageType); const tournamentNavbarRoutes: INavbarProps[] = navbarRoutes.map((item) => { if (item.name === "Mappool") { @@ -66,8 +62,8 @@ export default async function Layout({ children, params }: ITournamentLayout) { }) as INavbarProps[]; if ( - tournamentData.status && - tournamentData.response?.tournamentType !== tournamentType.PARTICIPANT_VS + tournamentData && + tournamentData?.tournamentType !== tournamentType.PARTICIPANT_VS ) { tournamentNavbarRoutes.push({ name: "Teams", diff --git a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx index d0b6637..81f1731 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx @@ -1,13 +1,9 @@ import React from "react"; import Link from "next/link"; -import { - type BeatmapModificationResponseDto, - MappoolService, - type StageResponseDto, -} from "../../../../../../../generated"; import { stageTypeEnumToString } from "@/lib/helpers"; import { MappoolCard } from "@ui/molecules/Cards/MappoolCard"; import Section from "@ui/atoms/Section/Section"; +import { BeatmapModificationResponseDto, getMappoolByStage, StageResponseDto } from '../../../../../../../client' const SingleTournamentMappool = async ({ params, @@ -23,14 +19,19 @@ const SingleTournamentMappool = async ({ }) => { let getMappoolByStageData, mappolStages; try { - getMappoolByStageData = await MappoolService.getMappoolByStage( - params.tournamentId, - params.stageType, + const { data: getMappoolByStageData1 } = await getMappoolByStage( + { + path: { + abbreviation: params.tournamentId, + stageType: params.stageType, + } + } ); - - mappolStages = getMappoolByStageData.beatmapsModifications.filter( + getMappoolByStageData = getMappoolByStageData1; + mappolStages = getMappoolByStageData?.beatmapsModifications.filter( (stage) => !!stage.beatmaps, ); + } catch (error) { console.error(error); } diff --git a/src/app/(tournament)/tournament/[tournamentId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/page.tsx index 9ded6ae..4e74ef9 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/page.tsx @@ -5,15 +5,6 @@ import { RiBarChartFill } from "react-icons/ri"; import { LiaLongArrowAltRightSolid } from "react-icons/lia"; import Link from "next/link"; import { format } from "date-fns"; -import { - AdminStaffMemberService, - MappoolService, - type StageResponseDto, - StageService, - type TeamResponseDto, - TeamService, - TournamentService, -} from "../../../../../generated"; import { TeamCard } from "@ui/molecules/Cards/TeamCard"; import { Socials } from "@ui/organisms/Socials/Socials"; import RegisterToTournamentButton from "@ui/molecules/RegisterToTournamentButton/RegisterToTournamentButton"; @@ -21,8 +12,17 @@ import { ScheduleList } from "@ui/organisms/ScheduleList/ScheduleList"; import { MappoolStages } from "@ui/organisms/MappoolStages/MappoolStages"; import { tournamentTeamShowEnumAvailable } from "@/lib/helpers"; import Section from "@ui/atoms/Section/Section"; -import { executeFetch } from "@/lib/executeFetch"; import StaffMember from "@ui/organisms/StaffMember/StaffMember"; +import { + getMappoolsByTournament, + getStaffMembers1, + getStages, + getTeamsByTournament, + getTournamentByAbbreviation, + StageResponseDto, + TeamResponseDto, + tournamentType, +} from "../../../../../client"; const SingleTournament = async ({ params, @@ -31,38 +31,47 @@ const SingleTournament = async ({ tournamentId: string; }; }) => { - const [getTournamentByAbbreviation, getStages, getStaffMembers] = await Promise.allSettled([ - TournamentService.getTournamentByAbbreviation(params.tournamentId), - StageService.getStages(params.tournamentId), - AdminStaffMemberService.getStaffMembers1(params.tournamentId), - ]); - - if (getTournamentByAbbreviation.status === "rejected") { - throw new Error("Tournament not found"); //todo: change to proper error - } - if (getStages.status === "rejected") { - throw new Error("Schedule not found"); //todo: change to proper error - } + const { data: getTournamentByAbbreviationData } = await getTournamentByAbbreviation({ + path: { + abbreviation: params.tournamentId, + }, + }); + const { data: getStagesData } = await getStages({ + path: { + abbreviation: params.tournamentId, + }, + }); + const { data: getStaffMembers } = await getStaffMembers1({ + path: { + abbreviation: params.tournamentId, + }, + }); let teams: TeamResponseDto[] = []; let teamSize = "1vs1"; // 1vs1 for participants if ( - tournamentTeamShowEnumAvailable.includes(getTournamentByAbbreviation.value?.tournamentType) + tournamentTeamShowEnumAvailable.includes( + getTournamentByAbbreviationData?.tournamentType as tournamentType, + ) ) { - teamSize = `${getTournamentByAbbreviation.value?.minimumTeamSize}vs${getTournamentByAbbreviation.value?.minimumTeamSize}`; - try { - teams = await TeamService.getTeamsByTournament(params.tournamentId); - } catch (error) { - throw new Error("Teams not found"); //todo: change to proper error - } + teamSize = `${getTournamentByAbbreviationData?.minimumTeamSize}vs${getTournamentByAbbreviationData?.minimumTeamSize}`; + + const { data: teamsData } = await getTeamsByTournament({ + path: { + abbreviation: params.tournamentId, + }, + }); + teams = teamsData || []; } - const mappools = await executeFetch( - MappoolService.getMappoolsByTournament(params.tournamentId), - ); + const { data: mappools } = await getMappoolsByTournament({ + path: { + abbreviation: params.tournamentId, + }, + }); const getStageByStageType = (stageType: string): StageResponseDto => { - return getStages.value.find( + return getStagesData?.find( (stage: StageResponseDto) => stage.stageType === stageType, ) as StageResponseDto; }; @@ -74,9 +83,9 @@ const SingleTournament = async ({

    - {getTournamentByAbbreviation?.value?.name} + {getTournamentByAbbreviationData?.name}

    - {getTournamentByAbbreviation?.value?.isOngoing && ( + {getTournamentByAbbreviationData?.isOngoing && (
    @@ -92,9 +101,11 @@ const SingleTournament = async ({ {/*)}*/} {/*Apply for staff*/} @@ -118,19 +129,18 @@ const SingleTournament = async ({ {" "} {format( - new Date(getTournamentByAbbreviation?.value?.startDate || 0), + new Date(getTournamentByAbbreviationData?.startDate || 0), "dd/MM/yyyy", )}{" "} -{" "} {format( - new Date(getTournamentByAbbreviation?.value?.endDate || 0), + new Date(getTournamentByAbbreviationData?.endDate || 0), "dd/MM/yyyy", )} - {" "} - {getTournamentByAbbreviation.value?.minimumRankLimit || 0} -{" "} - {getTournamentByAbbreviation.value?.maximumRankLimit || 0} + {getTournamentByAbbreviationData?.minimumRankLimit || 0}{" "} + - {getTournamentByAbbreviationData?.maximumRankLimit || 0}
    @@ -194,7 +204,7 @@ const SingleTournament = async ({
    - +
    - {mappools.status && mappools.response.length !== 0 ? ( + {mappools && mappools.length !== 0 ? (

    Mappool

    - {mappools.response.map((mappool) => { + {mappools?.map((mappool) => { const stage = getStageByStageType(mappool.stage); return (
    @@ -237,54 +247,50 @@ const SingleTournament = async ({
    ) : undefined} - {getTournamentByAbbreviation.value?.tournamentType === "TEAM_VS" && - teams.length > 0 && ( -
    -
    - 0 && ( +
    +
    + +

    -

    - Teams -

    {" "} - - -
    -
    - {teams.map((team, index) => { - if (index > 1) { - return null; + Teams + {" "} + -
    - -
    - - ); - })} -
    -
    - )} + /> + +
    +
    + {teams.map((team, index) => { + if (index > 1) { + return null; + } + return ( + +
    + +
    +
    + ); + })} +
    +
    + )}

    Prizes

    - {getTournamentByAbbreviation.value.prizePool?.map((prize) => ( + {getTournamentByAbbreviationData?.prizePool?.map((prize) => (
    - {getStaffMembers.status === "fulfilled" - ? getStaffMembers.value.map((member) => ( - - )) - : "Staff could not be loaded"} + {getStaffMembers?.map((member) => ( + + ))}
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx b/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx index 07d2e81..0b3f191 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx @@ -1,5 +1,5 @@ import React from "react"; - +// todo po co to jest>? export default function MainPageLayout({ children }: { children: React.ReactNode }) { return
    {children}
    ; } diff --git a/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx index 63ef905..afba387 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx @@ -1,8 +1,7 @@ import React from "react"; -import { TournamentService } from "../../../../../../generated"; -import { executeFetch } from "@/lib/executeFetch"; import "react-quill/dist/quill.snow.css"; import Section from "@ui/atoms/Section/Section"; +import { getTournamentByAbbreviation } from '../../../../../../client' const styles = "[&_h1]:text-4xl " + @@ -23,13 +22,12 @@ const SingleTournamentRules = async ({ tournamentId: string; }; }) => { - const tournament = await executeFetch( - TournamentService.getTournamentByAbbreviation(params.tournamentId), - ); + const { data:tournament } = await getTournamentByAbbreviation({ + path: { + abbreviation: params.tournamentId, + }, + }); - if (!tournament.status) { - return
    {tournament.errorMessage}
    ; - } return (
    @@ -42,65 +40,9 @@ const SingleTournamentRules = async ({
    - {/*
    */} - {/*

    Topic 1

    */} - {/*

    */} - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed*/} - {/* malesuada, tortor nec tincidunt tincidunt, libero nunc ullamcorper*/} - {/* nunc, nec interdum turpis libero a libero. Integer quis ex nec purus*/} - {/* lacinia ultricies. Nullam auctor, nisl vel ultricies condimentum,*/} - {/* purus nunc lacinia purus, nec ullamcorper libero ligula nec sapien.*/} - {/* Nunc id semper nunc, nec ullamcorper libero. Nullam auctor, nisl vel*/} - {/* ultricies condimentum, purus nunc lacinia purus, nec ullamcorper*/} - {/* libero ligula nec sapien. Nunc id semper nunc, nec ullamcorper*/} - {/* libero.*/} - {/*

    */} - {/*
    */} - {/*
    */} - {/*

    Topic 2

    */} - {/*

    */} - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed*/} - {/* malesuada, tortor nec tincidunt tincidunt, libero nunc ullamcorper*/} - {/* nunc, nec interdum turpis libero a libero. Integer quis ex nec purus*/} - {/* lacinia ultricies. Nullam auctor, nisl vel ultricies condimentum,*/} - {/* purus nunc lacinia purus, nec ullamcorper libero ligula nec sapien.*/} - {/* Nunc id semper nunc, nec ullamcorper libero. Nullam auctor, nisl vel*/} - {/* ultricies condimentum, purus nunc lacinia purus, nec ullamcorper*/} - {/* libero ligula nec sapien. Nunc id semper nunc, nec ullamcorper*/} - {/* libero.*/} - {/*

    */} - {/*
    */} - {/*
    */} - {/*

    Topic 3

    */} - {/*

    */} - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed*/} - {/* malesuada, tortor nec tincidunt tincidunt, libero nunc ullamcorper*/} - {/* nunc, nec interdum turpis libero a libero. Integer quis ex nec purus*/} - {/* lacinia ultricies. Nullam auctor, nisl vel ultricies condimentum,*/} - {/* purus nunc lacinia purus, nec ullamcorper libero ligula nec sapien.*/} - {/* Nunc id semper nunc, nec ullamcorper libero. Nullam auctor, nisl vel*/} - {/* ultricies condimentum, purus nunc lacinia purus, nec ullamcorper*/} - {/* libero ligula nec sapien. Nunc id semper nunc, nec ullamcorper*/} - {/* libero.*/} - {/*

    */} - {/*
    */} - {/*
    */} - {/*

    Topic 4

    */} - {/*

    */} - {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed*/} - {/* malesuada, tortor nec tincidunt tincidunt, libero nunc ullamcorper*/} - {/* nunc, nec interdum turpis libero a libero. Integer quis ex nec purus*/} - {/* lacinia ultricies. Nullam auctor, nisl vel ultricies condimentum,*/} - {/* purus nunc lacinia purus, nec ullamcorper libero ligula nec sapien.*/} - {/* Nunc id semper nunc, nec ullamcorper libero. Nullam auctor, nisl vel*/} - {/* ultricies condimentum, purus nunc lacinia purus, nec ullamcorper*/} - {/* libero ligula nec sapien. Nunc id semper nunc, nec ullamcorper*/} - {/* libero.*/} - {/*

    */} - {/*
    */}
    ); diff --git a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx index 9c4bb2a..c33db04 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { StageService } from "../../../../../../generated"; import { ScheduleList } from "@ui/organisms/ScheduleList/ScheduleList"; +import { getStages } from '../../../../../../client' const SingleTournamentSchedule = async ({ params, @@ -9,7 +9,11 @@ const SingleTournamentSchedule = async ({ tournamentId: string; }; }) => { - const data = await StageService.getStages(params.tournamentId); + const { data } = await getStages({ + path: { + abbreviation:params.tournamentId + } + }); return (
    Schedule
    - + ); diff --git a/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx index 7327171..fe9c54d 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx @@ -1,7 +1,7 @@ import React from "react"; -import { StaffMemberService } from "../../../../../../generated"; import StaffMember from "@ui/organisms/StaffMember/StaffMember"; import Section from "@ui/atoms/Section/Section"; +import { getStaffMembers } from '../../../../../../client' const SingleTournamentStaff = async ({ params, @@ -10,9 +10,13 @@ const SingleTournamentStaff = async ({ tournamentId: string; }; }) => { - const getStaffMembers = await StaffMemberService.getStaffMembers(params.tournamentId); + const { data:getStaffMembers1 } = await getStaffMembers({ + path: { + abbreviation: params.tournamentId, + }, + }); - const isStaff = getStaffMembers.some( + const isStaff = getStaffMembers1?.some( (staff) => staff.staffMembers && staff.staffMembers.length > 0, ); @@ -26,8 +30,8 @@ const SingleTournamentStaff = async ({ {!isStaff &&

    No staff members

    }
    {isStaff && - getStaffMembers - .sort((a, b) => { + getStaffMembers1 + ?.sort((a, b) => { if (a.position < b.position) { return -1; } diff --git a/src/lib/executeFetch.ts b/src/lib/executeFetch.ts deleted file mode 100644 index c12c2f6..0000000 --- a/src/lib/executeFetch.ts +++ /dev/null @@ -1,51 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { type ApiError, OpenAPI } from "../../generated"; -import { verifySession } from "@/lib/session"; - -export type SuccessfulResponse = { - status: true; - response: T; -}; - -export type ErrorResponse = { - status: false; - errorMessage: string; -}; - -export type ApiErrorMessages = { - errors: string[]; -}; - -export type Response = SuccessfulResponse | ErrorResponse; - -export const executeFetch = async ( - fetchRequest: Promise, - revalidatePaths?: string[], -): Promise> => { - "use server"; - const JWT = await verifySession(); - - if (typeof JWT.token === "string") { - OpenAPI.HEADERS = { - Cookie: `token=${JWT.token}`, - }; - } - - return fetchRequest - .then((res) => { - if (revalidatePaths) { - revalidatePaths.forEach((path) => { - revalidatePath(path); - }); - } - return { status: true, response: res } as SuccessfulResponse; - }) - .catch((error) => { - return { - status: false, - errorMessage: ((error as ApiError).body as ApiErrorMessages).errors[0], - } as ErrorResponse; - }); -}; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 0d9d4d8..f3eebd1 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -1,31 +1,31 @@ import { type MutableRefObject } from "react"; import { type z, type ZodObject, type ZodRawShape } from "zod"; -import { StageResponseDto, TournamentRequestDto } from "../../generated"; -import tournamentType = TournamentRequestDto.tournamentType; +import { type StageResponseDto, tournamentType } from "../../client"; +import { revalidatePath } from "next/cache"; export const stageTypeEnumToString = (stageType: StageResponseDto["stageType"]) => { switch (stageType) { - case StageResponseDto.stageType.REGISTRATION: + case "REGISTRATION": return "Registration"; - case StageResponseDto.stageType.QUALIFICATION: + case "QUALIFICATION": return "Qualification"; - case StageResponseDto.stageType.FINAL: + case "FINAL": return "Final"; - case StageResponseDto.stageType.RO16: + case "RO16": return "Round of 16"; - case StageResponseDto.stageType.RO64: + case "RO64": return "Round of 64"; - case StageResponseDto.stageType.GRAND_FINAL: + case "GRAND_FINAL": return "Grand Final"; - case StageResponseDto.stageType.QUARTER_FINAL: + case "QUARTER_FINAL": return "Quarter Final"; - case StageResponseDto.stageType.RO32: + case "RO32": return "Round of 32"; - case StageResponseDto.stageType.RO128: + case "RO128": return "Round of 128"; - case StageResponseDto.stageType.SEMI_FINAL: + case "SEMI_FINAL": return "Semi Final"; - case StageResponseDto.stageType.SCREENING: + case "SCREENING": return "Screening"; default: return "Unknown"; @@ -63,3 +63,10 @@ export const resetFormValues = >({ } }); }; + +export const multipleRevalidatePaths = (paths: string[]) => { + "use server"; + paths.forEach((path) => { + revalidatePath(path); + }); +}; diff --git a/src/lib/restClient.ts b/src/lib/restClient.ts new file mode 100644 index 0000000..04217a3 --- /dev/null +++ b/src/lib/restClient.ts @@ -0,0 +1,5 @@ +import { client } from "../../client"; + +client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, +}); diff --git a/src/middleware.ts b/src/middleware.ts index 7fb479f..924e00e 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,6 @@ import { type NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; +import { client } from "../client"; import { decrypt } from "@/lib/session"; // 1. Specify protected and public routes @@ -16,6 +17,16 @@ export default async function middleware(req: NextRequest) { const cookie = cookies().get("JWT")?.value; const session = await decrypt(cookie); + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Authorization: `Bearer ${session?.token as string}`, + }, + }); + // 4. Redirect if (isProtectedRoute && !session?.token) { return NextResponse.redirect(new URL("/register", req.nextUrl)); diff --git a/src/ui/molecules/LoginAvatar/LoginAvatar.tsx b/src/ui/molecules/LoginAvatar/LoginAvatar.tsx index 85d690f..724eba5 100644 --- a/src/ui/molecules/LoginAvatar/LoginAvatar.tsx +++ b/src/ui/molecules/LoginAvatar/LoginAvatar.tsx @@ -1,12 +1,11 @@ import React from "react"; import Link from "next/link"; import { redirect } from "next/navigation"; -import { DiscordService } from "../../../../generated"; +import { verify } from "../../../../client"; import { Button } from "@ui/atoms/Button/Button"; import { getUser } from "@/actions/public/getUserAction"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; import { LogoutButton } from "@ui/atoms/LogoutButton/LogoutButton"; -import { executeFetch } from "@/lib/executeFetch"; export const LoginAvatar = async () => { const userData = await getUser(); @@ -39,9 +38,9 @@ export const LoginAvatar = async () => { { "use server"; - const data = await executeFetch(DiscordService.verify(), ["/"]); - if (data.status) { - redirect(data.response.redirectUri); + const { data } = await verify(); + if (data?.redirectUri) { + redirect(data.redirectUri); } }} > diff --git a/src/ui/organisms/Footer/Footer.tsx b/src/ui/organisms/Footer/Footer.tsx index b22f4d8..31df90e 100644 --- a/src/ui/organisms/Footer/Footer.tsx +++ b/src/ui/organisms/Footer/Footer.tsx @@ -4,10 +4,10 @@ import { SiKofi } from "react-icons/si"; import { FaDiscord, FaTwitch } from "react-icons/fa"; import { FaXTwitter } from "react-icons/fa6"; import Link from "next/link"; -import { TournamentService } from "../../../../generated"; +import { getTournaments } from "../../../../client"; export const Footer = async () => { - const tournaments = await TournamentService.getTournaments(); + const { data } = await getTournaments(); return (
    @@ -32,7 +32,7 @@ export const Footer = async () => {

    My profile

    {/*TODO: Ukryte gdy użytkownik niezalogowany*/}
    - {tournaments.map((tournament) => ( + {data?.map((tournament) => ( { const scheduleListContent = scheduleList?.map((stage) => { diff --git a/src/ui/organisms/TournamentList/TournamentList.tsx b/src/ui/organisms/TournamentList/TournamentList.tsx index 9a94fe2..de68c2e 100644 --- a/src/ui/organisms/TournamentList/TournamentList.tsx +++ b/src/ui/organisms/TournamentList/TournamentList.tsx @@ -1,10 +1,10 @@ import React from "react"; import { type EmblaOptionsType } from "embla-carousel"; import { format } from "date-fns"; -import { type TournamentResponseDto, TournamentService, UserService } from "../../../../generated"; +import { getTournaments, getUserTournaments, type TournamentResponseDto } from "../../../../client"; import { Carousel } from "@ui/organisms/Carousel/Carousel"; import { TournamentCard } from "@ui/molecules/Cards/TournamentCard"; -import { executeFetch } from "@/lib/executeFetch"; + const OPTIONS: EmblaOptionsType = { loop: false }; export const TournamentList = async ({ @@ -15,21 +15,21 @@ export const TournamentList = async ({ /** Get all tournaments associated with the active user */ userTournaments?: boolean; }) => { - let data: TournamentResponseDto[] | undefined; + let tournamentData: TournamentResponseDto[] | undefined; if (userTournaments) { - const userTournamentData = await executeFetch(UserService.getUserTournaments()); - if (userTournamentData.status) { - data = userTournamentData.response; + const { data, error } = await getUserTournaments(); + if (!error) { + tournamentData = data; } } else { - const tournamentData = await executeFetch(TournamentService.getTournaments()); - if (tournamentData.status) { - data = tournamentData.response; + const { data, error } = await getTournaments(); + if (!error) { + tournamentData = data; } } - const tournamentSlices = data?.map((tournament) => { + const tournamentSlices = tournamentData?.map((tournament) => { return ( Date: Fri, 16 Aug 2024 11:20:21 +0200 Subject: [PATCH 07/37] chore: added teams delete action --- .../[tournamentAbbreviation]/teams/page.tsx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx index 50cd24e..5d156cc 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx @@ -134,6 +134,30 @@ const TeamsPage = async ({ REJECT +
    { + "use server"; + await executeFetch( + AdminTeamService.deleteTeam( + tournamentAbbreviation, + team.id, + ), + [ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${team.id}`, + `/account/`, + ], + ); + }} + > + +
    ); From 4fc15ff98a6d838980d556edd74dc8dd1af76963 Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 16 Aug 2024 12:57:53 +0200 Subject: [PATCH 08/37] chore: remove cache from getUser --- src/actions/public/getUserAction.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/actions/public/getUserAction.ts b/src/actions/public/getUserAction.ts index 978bff0..89af9cb 100644 --- a/src/actions/public/getUserAction.ts +++ b/src/actions/public/getUserAction.ts @@ -1,8 +1,7 @@ -import { cache } from "react"; import { OpenAPI, UserService } from "../../../generated"; import { verifySession } from "@/lib/session"; -export const getUser = cache(async () => { +export const getUser = async () => { const JWT = await verifySession(); if (typeof JWT.token === "string" && JWT.isAuth) { OpenAPI.HEADERS = { @@ -12,4 +11,4 @@ export const getUser = cache(async () => { return userData; } return null; -}); +}; From bba68969201c00803ab652ee204fbb5bac7b9506 Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 16 Aug 2024 15:35:33 +0200 Subject: [PATCH 09/37] fix: fixed requests --- src/actions/admin/adminBeatMapActions.ts | 14 +- .../admin/adminQualificationRoomsActions.ts | 31 +++- src/actions/admin/adminStaffMembersActions.ts | 53 +++++- src/actions/admin/adminStageActions.ts | 31 +++- src/actions/admin/editTournamentAction.ts | 18 +- .../admin/editTournamentImageAction.ts | 18 +- src/actions/public/createTeamAction.ts | 166 ++++++++++++------ src/actions/public/createTournamentAction.ts | 64 ++++--- src/actions/public/getUserAction.ts | 17 +- .../participianJoinToTheTournamentAction.ts | 44 ++--- .../dashboard/CreateTournamentModal.tsx | 2 +- .../[tournamentAbbreviation]/layout.tsx | 15 +- .../mappool/[stageType]/[mappoolId]/page.tsx | 25 ++- .../qualification-rooms/page.tsx | 52 ++++-- .../settings/page.tsx | 2 +- .../staff-members/StaffMemberModal.tsx | 4 +- .../UserLessStaffMemberModal.tsx | 2 +- .../staff-members/page.tsx | 32 +++- .../[tournamentAbbreviation]/stages/page.tsx | 33 +++- .../[tournamentAbbreviation]/teams/page.tsx | 73 +++++++- .../(main-page)/(withAuth)/dashboard/page.tsx | 16 +- src/app/(main-page)/layout.tsx | 12 ++ src/app/(main-page)/page.tsx | 12 ++ .../tournament/[tournamentId]/layout.tsx | 31 +++- .../mappool/[stageType]/page.tsx | 38 ++-- .../tournament/[tournamentId]/page.tsx | 38 ++-- .../[tournamentId]/registration/layout.tsx | 12 ++ .../tournament/[tournamentId]/rules/page.tsx | 16 +- .../[tournamentId]/schedule/page.tsx | 17 +- .../tournament/[tournamentId]/staff/page.tsx | 15 +- .../teams/[teamId]/ChangeTeamNameForm.tsx | 4 +- .../[teamId]/InvitePlayerToTeamButton.tsx | 4 +- .../[tournamentId]/teams/[teamId]/page.tsx | 117 ++++++++---- .../tournament/[tournamentId]/teams/page.tsx | 27 ++- src/app/layout.tsx | 18 ++ src/lib/helpers.ts | 8 - src/lib/multipleRevalidatePaths.ts | 9 + src/lib/session.ts | 66 +++---- src/middleware.ts | 18 +- src/ui/molecules/LoginAvatar/LoginAvatar.tsx | 13 +- .../RegisterToTournamentButton.tsx | 11 +- .../organisms/MappoolStages/MappoolStages.tsx | 3 +- .../organisms/ScheduleList/ScheduleList.tsx | 2 +- .../TournamentList/TournamentList.tsx | 18 +- .../UserInformation/UserInformation.tsx | 12 ++ .../organisms/UserInformation/UserTeams.tsx | 22 ++- 46 files changed, 925 insertions(+), 330 deletions(-) create mode 100644 src/lib/multipleRevalidatePaths.ts diff --git a/src/actions/admin/adminBeatMapActions.ts b/src/actions/admin/adminBeatMapActions.ts index 3141838..e85722a 100644 --- a/src/actions/admin/adminBeatMapActions.ts +++ b/src/actions/admin/adminBeatMapActions.ts @@ -1,10 +1,20 @@ "use server"; -import { addBeatmap, type modification } from "../../../client"; +import { cookies } from "next/headers"; +import { addBeatmap, client, type modification } from "../../../client"; import { type AddBeatMapSchemaType } from "@/formSchemas/addBeatMapSchema"; export async function addBeatMapAction(formData: AddBeatMapSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await addBeatmap({ path: { abbreviation: formData.tournamentAbb, diff --git a/src/actions/admin/adminQualificationRoomsActions.ts b/src/actions/admin/adminQualificationRoomsActions.ts index 58a2f1d..ec6822a 100644 --- a/src/actions/admin/adminQualificationRoomsActions.ts +++ b/src/actions/admin/adminQualificationRoomsActions.ts @@ -1,13 +1,23 @@ "use server"; +import { cookies } from "next/headers"; +import { client, createQualificationRooms, updateQualificationRoom } from "../../../client"; import { type CreateQualificationRoomsSchemaType } from "@/formSchemas/createQualificationRoomsSchema"; import { type EditQualificationRoomsSchemaType } from "@/formSchemas/editQualificationRoomSchema"; -import { createQualificationRooms, updateQualificationRoom } from "../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; export async function createQualificationRoomsAction(formData: CreateQualificationRoomsSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await createQualificationRooms({ path: { abbreviation: formData.tournamentAbbreviation, @@ -26,7 +36,7 @@ export async function createQualificationRoomsAction(formData: CreateQualificati }; } - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${formData.tournamentAbbreviation}/qualification-rooms`, ]); @@ -39,7 +49,16 @@ export async function createQualificationRoomsAction(formData: CreateQualificati export async function editQualificationRoomsAction(formData: EditQualificationRoomsSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await updateQualificationRoom({ path: { abbreviation: formData.tournamentAbbreviation, @@ -59,7 +78,7 @@ export async function editQualificationRoomsAction(formData: EditQualificationRo }; } - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${formData.tournamentAbbreviation}/qualification-rooms`, ]); diff --git a/src/actions/admin/adminStaffMembersActions.ts b/src/actions/admin/adminStaffMembersActions.ts index ae59a7e..eeb87e5 100644 --- a/src/actions/admin/adminStaffMembersActions.ts +++ b/src/actions/admin/adminStaffMembersActions.ts @@ -1,13 +1,23 @@ "use server"; +import { cookies } from "next/headers"; +import { addStaffMembers, client, updateStaffMembers } from "../../../client"; import { type AddStaffMembersSchemaType } from "@/formSchemas/addEditStaffMembersSchema"; import { type AddUserLessStaffMembersSchemaType } from "@/formSchemas/addEditUserLessStaffMembersSchema"; -import { addStaffMembers, updateStaffMembers } from "../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; export async function editStaffMemberAction(formData: AddStaffMembersSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await updateStaffMembers({ path: { staffMemberId: formData.osuId, @@ -27,7 +37,10 @@ export async function editStaffMemberAction(formData: AddStaffMembersSchemaType) }; } - multipleRevalidatePaths(["/", `/dashboard/${formData.tournamentAbbreviation}/staff-members`]); + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/staff-members`, + ]); return { status: true as const, @@ -37,7 +50,16 @@ export async function editStaffMemberAction(formData: AddStaffMembersSchemaType) export async function addStaffMemberAction(formData: AddStaffMembersSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await addStaffMembers({ path: { abbreviation: formData.tournamentAbbreviation, @@ -57,7 +79,10 @@ export async function addStaffMemberAction(formData: AddStaffMembersSchemaType) }; } - multipleRevalidatePaths(["/", `/dashboard/${formData.tournamentAbbreviation}/staff-members`]); + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/staff-members`, + ]); return { status: true as const, @@ -67,7 +92,16 @@ export async function addStaffMemberAction(formData: AddStaffMembersSchemaType) export async function addUserLessStaffMemberAction(formData: AddUserLessStaffMembersSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await addStaffMembers({ path: { abbreviation: formData.tournamentAbbreviation, @@ -87,7 +121,10 @@ export async function addUserLessStaffMemberAction(formData: AddUserLessStaffMem }; } - multipleRevalidatePaths(["/", `/dashboard/${formData.tournamentAbbreviation}/staff-members`]); + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/staff-members`, + ]); return { status: true as const, diff --git a/src/actions/admin/adminStageActions.ts b/src/actions/admin/adminStageActions.ts index e9b8489..486e349 100644 --- a/src/actions/admin/adminStageActions.ts +++ b/src/actions/admin/adminStageActions.ts @@ -1,15 +1,25 @@ "use server"; +import { cookies } from "next/headers"; +import { client, createStage, type stageType, updateStage } from "../../../client"; import { type CreateStageSchemaType, type EditStageSchemaType, } from "@/formSchemas/createStageSchema"; -import { createStage, stageType, updateStage } from "../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; export async function createStageAction(formData: CreateStageSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await createStage({ path: { abbreviation: formData.tournamentAbb, @@ -28,7 +38,7 @@ export async function createStageAction(formData: CreateStageSchemaType) { }; } - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${formData.tournamentAbb}`, `/tournament/${formData.tournamentAbb}`, @@ -41,7 +51,16 @@ export async function createStageAction(formData: CreateStageSchemaType) { } export async function editStageAction(formData: EditStageSchemaType) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await updateStage({ path: { abbreviation: formData.tournamentAbb, @@ -60,7 +79,7 @@ export async function editStageAction(formData: EditStageSchemaType) { }; } - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${formData.tournamentAbb}`, `/tournament/${formData.tournamentAbb}`, diff --git a/src/actions/admin/editTournamentAction.ts b/src/actions/admin/editTournamentAction.ts index 69123a1..dd01cbe 100644 --- a/src/actions/admin/editTournamentAction.ts +++ b/src/actions/admin/editTournamentAction.ts @@ -1,12 +1,22 @@ "use server"; +import { cookies } from "next/headers"; +import { client, updateTournament } from "../../../client"; import { type EditTournamentSchemaType } from "@/formSchemas/editTournamentSchema"; -import { updateTournament } from "../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; export async function editTournamentAction(formData: EditTournamentSchemaType, formRules: string) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await updateTournament({ path: { abbreviation: formData.oldAbbreviation, @@ -30,7 +40,7 @@ export async function editTournamentAction(formData: EditTournamentSchemaType, f }; } - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${formData.abbreviation}`, `/dashboard/${formData.abbreviation}/settings`, diff --git a/src/actions/admin/editTournamentImageAction.ts b/src/actions/admin/editTournamentImageAction.ts index 4fad62a..49f1598 100644 --- a/src/actions/admin/editTournamentImageAction.ts +++ b/src/actions/admin/editTournamentImageAction.ts @@ -1,11 +1,21 @@ "use server"; -import { updateTournamentImage } from "../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { cookies } from "next/headers"; +import { client, updateTournamentImage } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; export async function editTournamentImageAction(formData: FormData) { "use server"; - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const image = formData.get("image"); if (!image) { return { @@ -40,7 +50,7 @@ export async function editTournamentImageAction(formData: FormData) { }; } - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${abbreviation as string}`, `/tournament/${abbreviation as string}`, diff --git a/src/actions/public/createTeamAction.ts b/src/actions/public/createTeamAction.ts index d07d82d..74c09ec 100644 --- a/src/actions/public/createTeamAction.ts +++ b/src/actions/public/createTeamAction.ts @@ -1,73 +1,139 @@ "use server"; -import { type TeamResponseDto, TeamService } from "../../../generated"; +import { cookies } from "next/headers"; +import { client, createTeam, inviteParticipant, updateTeam } from "../../../client"; import { type CreateTeamSchemaType } from "@/formSchemas/createTeamSchema"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; import { type updateTeamSchemaType } from "@/formSchemas/updateTeamSchema"; import { type inviteToTeamSchemaType } from "@/formSchemas/inviteToTeamSchema"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; -export async function createTeamAction(data: CreateTeamSchemaType) { +export async function createTeamAction(formData: CreateTeamSchemaType) { "use server"; - if (data.terms === "off") { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + + if (formData.terms === "off") { return { - status: false, + status: false as const, errorMessage: "You need to accept the terms and conditions", }; } - return executeFetch( - TeamService.createTeam(data.tournamentAbb, { - name: data.teamName, + const { data, error } = await createTeam({ + path: { + abbreviation: formData.tournamentAbb, + }, + body: { + name: formData.teamName, logoUrl: "", - }), - ["/", `/tournament/${data.tournamentAbb}`], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths([ + "/", + `/tournament/${formData.tournamentAbb}`, + `/tournament/${formData.tournamentAbb}/teams`, + ]); + + return { + status: true as const, + response: data, + }; } -export async function updateTeam(data: updateTeamSchemaType) { +export async function updateTeamAction(formData: updateTeamSchemaType) { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data, error } = await updateTeam({ + path: { + abbreviation: formData.tournamentAbbreviation, + teamId: formData.teamId, + }, + body: { + name: formData.name, + logoUrl: formData.logoUrl, + }, + }); - return executeFetch( - TeamService.updateTeam(data.tournamentAbbreviation, data.teamId, { - name: data.name, - logoUrl: data.logoUrl, - }), - [ - "/", - `/tournament/${data.tournamentAbbreviation}/teams/${data.teamId}`, - `/tournament/${data.tournamentAbbreviation}`, - ], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths([ + "/", + `/tournament/${formData.tournamentAbbreviation}/teams/${formData.teamId}`, + `/tournament/${formData.tournamentAbbreviation}`, + ]); + + return { + status: true as const, + response: data, + }; } -export async function inviteToTeam(data: inviteToTeamSchemaType) { +export async function inviteToTeamAction(formData: inviteToTeamSchemaType) { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data, error } = await inviteParticipant({ + path: { + abbreviation: formData.tournamentAbbreviation, + teamId: formData.teamId, + osuId: formData.osuId, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths([ + "/", + `/tournament/${formData.tournamentAbbreviation}/teams/${formData.teamId}`, + `/tournament/${formData.tournamentAbbreviation}`, + ]); - return executeFetch( - TeamService.inviteParticipant(data.tournamentAbbreviation, data.teamId, data.osuId), - [ - "/", - `/tournament/${data.tournamentAbbreviation}/teams/${data.teamId}`, - `/tournament/${data.tournamentAbbreviation}`, - ], - ) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/public/createTournamentAction.ts b/src/actions/public/createTournamentAction.ts index df22e53..6ed8292 100644 --- a/src/actions/public/createTournamentAction.ts +++ b/src/actions/public/createTournamentAction.ts @@ -1,29 +1,51 @@ "use server"; +import { cookies } from "next/headers"; import { - AdminTournamentService, - type TournamentRequestDto, - type TournamentResponseDto, -} from "../../../generated"; + client, + createTournament, + type qualificationType, + type tournamentType, +} from "../../../client"; import { type CreateTournamentSchemaType } from "@/formSchemas/createTournamentSchema"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; -export async function createTournamentAction(data: CreateTournamentSchemaType) { +export async function createTournamentAction(formData: CreateTournamentSchemaType) { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data, error } = await createTournament({ + body: { + name: formData.name, + abbreviation: formData.abbreviation, + tournamentType: formData.tournamentType as tournamentType, + qualificationType: formData.qualificationType as qualificationType, + minimumRankLimit: +formData.minimumRankLimit, + maximumRankLimit: +formData.maximumRankLimit, + minimumTeamSize: +formData.minimumTeamSize, + maximumTeamSize: +formData.maximumTeamSize, + }, + }); - return executeFetch( - AdminTournamentService.createTournament({ - name: data.name, - abbreviation: data.abbreviation, - tournamentType: data.tournamentType as TournamentRequestDto.tournamentType, - qualificationType: data.qualificationType as TournamentRequestDto.qualificationType, - minimumRankLimit: +data.minimumRankLimit, - maximumRankLimit: +data.maximumRankLimit, - minimumTeamSize: +data.minimumTeamSize, - maximumTeamSize: +data.maximumTeamSize, - }), - ["/", "/dashboard"], - ) - .then((res) => res as SuccessfulResponse) - .catch((error) => error as ErrorResponse); + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths(["/", "/dashboard"]); + + return { + status: true as const, + response: data, + }; } diff --git a/src/actions/public/getUserAction.ts b/src/actions/public/getUserAction.ts index f10ad45..29291c0 100644 --- a/src/actions/public/getUserAction.ts +++ b/src/actions/public/getUserAction.ts @@ -1,10 +1,19 @@ import { cache } from "react"; -import { me } from "../../../client"; -import { verifySession } from "@/lib/session"; +import { cookies } from "next/headers"; +import { client, me } from "../../../client"; export const getUser = cache(async () => { - const JWT = await verifySession(); - if (typeof JWT.token === "string" && JWT.isAuth) { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + if (cookie) { const { data } = await me(); return data; } diff --git a/src/actions/public/participianJoinToTheTournamentAction.ts b/src/actions/public/participianJoinToTheTournamentAction.ts index 44f7b37..3b3d15e 100644 --- a/src/actions/public/participianJoinToTheTournamentAction.ts +++ b/src/actions/public/participianJoinToTheTournamentAction.ts @@ -1,22 +1,24 @@ -"use server"; +// "use server"; +// +// import { type ParticipantResponseDto, ParticipantService } from "../../../generated"; +// import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; +// +// export async function participianJoinToTheTournamentAction(data: FormData) { +// "use server"; +// const tournamentAbb = data.get("tournamentAbb") as string; +// +// return executeFetch(ParticipantService.registerParticipant(tournamentAbb), [ +// "/", +// "/dashboard", +// "/tournament", +// "/registration", +// ]) +// .then((res) => { +// return res as SuccessfulResponse; +// }) +// .catch((error) => { +// return error as ErrorResponse; +// }); +// } -import { type ParticipantResponseDto, ParticipantService } from "../../../generated"; -import { type ErrorResponse, executeFetch, type SuccessfulResponse } from "@/lib/executeFetch"; - -export async function participianJoinToTheTournamentAction(data: FormData) { - "use server"; - const tournamentAbb = data.get("tournamentAbb") as string; - - return executeFetch(ParticipantService.registerParticipant(tournamentAbb), [ - "/", - "/dashboard", - "/tournament", - "/registration", - ]) - .then((res) => { - return res as SuccessfulResponse; - }) - .catch((error) => { - return error as ErrorResponse; - }); -} +//todo diff --git a/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx index bdb7ee8..2f431af 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal.tsx @@ -4,6 +4,7 @@ import React, { useRef } from "react"; import { IoMdAdd } from "react-icons/io"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; +import { qualificationType, tournamentType } from '../../../../../client' import { Button } from "@ui/atoms/Button/Button"; import Modal from "@ui/organisms/Modal/Modal"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; @@ -11,7 +12,6 @@ import { createTournamentSchema } from "@/formSchemas/createTournamentSchema"; import { Input } from "@ui/atoms/Forms/Input/Input"; import { ComboBox, type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; import { createTournamentAction } from "@/actions/public/createTournamentAction"; -import { qualificationType, tournamentType } from '../../../../../client' const CreateTournamentModal = () => { const modalRef = useRef(null); diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/layout.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/layout.tsx index bfbc597..58cef34 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/layout.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/layout.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { getTournamentByAbbreviation, tournamentType } from '../../../../../../client' +import { cookies } from "next/headers"; +import { client, getTournamentByAbbreviation, tournamentType } from "../../../../../../client"; export default async function Layout({ children, @@ -10,7 +11,17 @@ export default async function Layout({ tournamentAbbreviation: string; }; }) { - const { data:tournamentData } = await getTournamentByAbbreviation({ + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: tournamentData } = await getTournamentByAbbreviation({ path: { abbreviation: tournamentAbbreviation, }, diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx index f6e5b65..db292d1 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx @@ -1,14 +1,17 @@ import React from "react"; import Image from "next/image"; -import { multipleRevalidatePaths, stageTypeEnumToString } from "@/lib/helpers"; -import { Button } from "@ui/atoms/Button/Button"; -import { AddBeatMap } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap"; +import { cookies } from "next/headers"; import { + client, deleteBeatmap, getMappool, releaseMappool, - stageType, + type stageType, } from "../../../../../../../../../client"; +import { stageTypeEnumToString } from "@/lib/helpers"; +import { Button } from "@ui/atoms/Button/Button"; +import { AddBeatMap } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; const StageTypePage = async ({ params: { tournamentAbbreviation, stageType, mappoolId }, @@ -39,6 +42,16 @@ const StageTypePage = async ({ className={"mb-3"} action={async (_e) => { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await releaseMappool({ path: { stageType, @@ -49,7 +62,7 @@ const StageTypePage = async ({ }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/mappool/${stageType}/${mappoolId}`, ]); @@ -132,7 +145,7 @@ const StageTypePage = async ({ beatmapId: beatmap.id, }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/mappool/${stageType}/${mappoolId}`, ]); diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx index 350d8bd..581a945 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx @@ -1,10 +1,9 @@ import React from "react"; import Image from "next/image"; import { format } from "date-fns"; -import { QualificationRoomModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal"; -import { type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; -import { getUser } from "@/actions/public/getUserAction"; +import { cookies } from "next/headers"; import { + client, deleteQualificationRoom, getParticipants, getQualificationRooms, @@ -12,14 +11,17 @@ import { getTeamsByTournament, getTournamentByAbbreviation, getTournamentStaffMember, - ParticipantResponseDto, - QualificationRoomResponseDto, + type ParticipantResponseDto, + type QualificationRoomResponseDto, signInQualificationRoom, - StaffMemberResponseDto, - TeamResponseDto, + type StaffMemberResponseDto, + type TeamResponseDto, tournamentType, } from "../../../../../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { QualificationRoomModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal"; +import { type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; +import { getUser } from "@/actions/public/getUserAction"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; interface QualificationRoom extends QualificationRoomResponseDto { id: string; @@ -58,6 +60,16 @@ const QRoomsPage = async ({ tournamentAbbreviation: string; }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const userData = await getUser(); const { data: getStaffForATournamentUser } = await getTournamentStaffMember({ path: { @@ -197,13 +209,23 @@ const QRoomsPage = async ({
    { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await signInQualificationRoom({ path: { abbreviation: tournamentAbbreviation, roomId: room.id, }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/qualification-rooms`, ]); @@ -258,13 +280,23 @@ const QRoomsPage = async ({ { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await deleteQualificationRoom({ path: { abbreviation: tournamentAbbreviation, roomId: room.id, }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/qualification-rooms`, ]); diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx index 8fb4838..0ec363c 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx @@ -3,6 +3,7 @@ import React, { useEffect } from "react"; import { useParams, useRouter } from "next/navigation"; import { toast } from "sonner"; import ReactQuill from "react-quill"; +import { getTournamentByAbbreviation, type TournamentResponseDto } from "../../../../../../../client"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; import { editTournamentSchema } from "@/formSchemas/editTournamentSchema"; import { Input } from "@ui/atoms/Forms/Input/Input"; @@ -10,7 +11,6 @@ import { Button } from "@ui/atoms/Button/Button"; import "react-quill/dist/quill.snow.css"; import { editTournamentAction } from "@/actions/admin/editTournamentAction"; import { editTournamentImageAction } from "@/actions/admin/editTournamentImageAction"; -import { getTournamentByAbbreviation, TournamentResponseDto } from "../../../../../../../client"; const SettingsPage = () => { const [tournamentData, setTournamentData] = React.useState(null); diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/StaffMemberModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/StaffMemberModal.tsx index a300e5f..073ea29 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/StaffMemberModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/StaffMemberModal.tsx @@ -57,7 +57,7 @@ export const StaffMemberModal = ({ async (data) => { const addStaffMemberResponse = await addStaffMemberAction(data); if (!addStaffMemberResponse.status) { - return toast.error(addStaffMemberResponse.errorMessage, { + return toast.error("Error", { duration: 3000, }); } @@ -75,7 +75,7 @@ export const StaffMemberModal = ({ async (data) => { const editMemberResponse = await editStaffMemberAction(data); if (!editMemberResponse.status) { - return toast.error(editMemberResponse.errorMessage, { + return toast.error("error", { duration: 3000, }); } diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/UserLessStaffMemberModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/UserLessStaffMemberModal.tsx index 6b13f68..c96c1e3 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/UserLessStaffMemberModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/UserLessStaffMemberModal.tsx @@ -46,7 +46,7 @@ export const UserLessStaffMemberModal = ({ async (data) => { const addStaffMemberResponse = await addUserLessStaffMemberAction(data); if (!addStaffMemberResponse.status) { - return toast.error(addStaffMemberResponse.errorMessage, { + return toast.error("error", { duration: 3000, }); } diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/page.tsx index 1781e08..20bb5f7 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/page.tsx @@ -1,15 +1,17 @@ import React from "react"; import Image from "next/image"; -import { StaffMemberModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/StaffMemberModal"; -import type { selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; -import { UserLessStaffMemberModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/UserLessStaffMemberModal"; +import { cookies } from "next/headers"; import { + client, deleteStaffMembers, getStaffMembers1, getTournamentPermissions, getTournamentRoles, } from "../../../../../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { StaffMemberModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/StaffMemberModal"; +import type { selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; +import { UserLessStaffMemberModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/staff-members/UserLessStaffMemberModal"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; const StaffMembersPage = async ({ params: { tournamentAbbreviation }, @@ -18,6 +20,16 @@ const StaffMembersPage = async ({ tournamentAbbreviation: string; }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data: getRoles } = await getTournamentRoles({ path: { abbreviation: tournamentAbbreviation, @@ -155,13 +167,23 @@ const StaffMembersPage = async ({ { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await deleteStaffMembers({ path: { abbreviation: tournamentAbbreviation, staffMemberId: s.id, }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/staff-members`, ]); diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/stages/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/stages/page.tsx index 2e5dde2..29b61c5 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/stages/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/stages/page.tsx @@ -1,14 +1,11 @@ import React from "react"; import { format } from "date-fns"; import Link from "next/link"; +import { cookies } from "next/headers"; +import { client, deleteStage, getStages, stageType } from "../../../../../../../client"; import { StageForm } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/stages/StageForm"; -import { multipleRevalidatePaths, stageTypeEnumToString } from "@/lib/helpers"; -import { - deleteStage, - getStages, - getTournamentByAbbreviation, - stageType, -} from "../../../../../../../client"; +import { stageTypeEnumToString } from "@/lib/helpers"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; const StagePage = async ({ params: { tournamentAbbreviation }, @@ -17,6 +14,16 @@ const StagePage = async ({ tournamentAbbreviation: string; }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data: getStagesData } = await getStages({ path: { abbreviation: tournamentAbbreviation, @@ -80,13 +87,23 @@ const StagePage = async ({ { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await deleteStage({ path: { abbreviation: tournamentAbbreviation, stageType: stage.stageType, }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/stages`, ]); diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx index d3debdd..ee3f452 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx @@ -1,7 +1,8 @@ import React from "react"; import Image from "next/image"; -import { changeTeamStatus, getTeams } from "../../../../../../../client"; -import { multipleRevalidatePaths } from "@/lib/helpers"; +import { cookies } from "next/headers"; +import { changeTeamStatus, client, deleteTeam, getTeams } from "../../../../../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; const TeamsPage = async ({ params: { tournamentAbbreviation }, @@ -10,6 +11,16 @@ const TeamsPage = async ({ tournamentAbbreviation: string; }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data, error } = await getTeams({ path: { abbreviation: tournamentAbbreviation, @@ -91,6 +102,16 @@ const TeamsPage = async ({ { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await changeTeamStatus({ path: { abbreviation: tournamentAbbreviation, @@ -100,7 +121,7 @@ const TeamsPage = async ({ status: "ACCEPTED", }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/teams`, `/tournament/${tournamentAbbreviation}/teams/${team.id}`, @@ -118,6 +139,16 @@ const TeamsPage = async ({ { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); await changeTeamStatus({ path: { abbreviation: tournamentAbbreviation, @@ -127,7 +158,7 @@ const TeamsPage = async ({ status: "REJECTED", }, }); - multipleRevalidatePaths([ + await multipleRevalidatePaths([ "/", `/dashboard/${tournamentAbbreviation}/teams`, `/tournament/${tournamentAbbreviation}/teams/${team.id}`, @@ -142,6 +173,40 @@ const TeamsPage = async ({ REJECT +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await deleteTeam({ + path: { + abbreviation: tournamentAbbreviation, + teamId: team.id, + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${team.id}`, + `/account/`, + ]); + }} + > + +
    ); diff --git a/src/app/(main-page)/(withAuth)/dashboard/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/page.tsx index f4380d0..4f26dab 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/page.tsx @@ -1,11 +1,21 @@ import React from "react"; import Link from "next/link"; +import { cookies } from "next/headers"; +import { client, getTournaments } from "../../../../../client"; import CreateTournamentModal from "@/app/(main-page)/(withAuth)/dashboard/CreateTournamentModal"; -import { getTournaments } from '../../../../../client' const DashboardPage = async () => { - const { data:tournaments } = await getTournaments(); - + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: tournaments } = await getTournaments(); return (
    diff --git a/src/app/(main-page)/layout.tsx b/src/app/(main-page)/layout.tsx index 9aeae6f..c23c6a1 100644 --- a/src/app/(main-page)/layout.tsx +++ b/src/app/(main-page)/layout.tsx @@ -1,11 +1,23 @@ import React from "react"; import NextTopLoader from "nextjs-toploader"; +import { cookies } from "next/headers"; +import { client } from "../../../client"; import { Navbar } from "@ui/organisms/Navbar/Navbar"; import { Footer } from "@ui/organisms/Footer/Footer"; import { navigationList } from "@/lib/mainNavigation"; import { LoginAvatar } from "@ui/molecules/LoginAvatar/LoginAvatar"; export default function MainPageLayout({ children }: { children: React.ReactNode }) { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); return ( <> diff --git a/src/app/(main-page)/page.tsx b/src/app/(main-page)/page.tsx index a82ee09..ed7054a 100644 --- a/src/app/(main-page)/page.tsx +++ b/src/app/(main-page)/page.tsx @@ -2,11 +2,23 @@ import React, { Suspense } from "react"; import Image from "next/image"; import { MdKeyboardArrowDown } from "react-icons/md"; import Link from "next/link"; +import { cookies } from "next/headers"; +import { client } from "../../../client"; import { TournamentList } from "@ui/organisms/TournamentList/TournamentList"; import { Loading } from "@ui/atoms/Loading/Loading"; import Section from "@ui/atoms/Section/Section"; const Main = async () => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); return ( <>
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/layout.tsx b/src/app/(tournament)/tournament/[tournamentId]/layout.tsx index fea2311..000feb5 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/layout.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/layout.tsx @@ -1,11 +1,17 @@ import React from "react"; import Image from "next/image"; import NextTopLoader from "nextjs-toploader"; +import { cookies } from "next/headers"; +import { + client, + getStages, + getTournamentByAbbreviation, + tournamentType, +} from "../../../../../client"; import { type INavbarProps, Navbar } from "@ui/organisms/Navbar/Navbar"; import { Footer } from "@ui/organisms/Footer/Footer"; import { stageTypeEnumToString } from "@/lib/helpers"; import { LoginAvatar } from "@ui/molecules/LoginAvatar/LoginAvatar"; -import { getStages, getTournamentByAbbreviation, tournamentType } from '../../../../../client' type ITournamentLayout = { children: React.ReactNode; @@ -27,16 +33,26 @@ const navbarRoutes: INavbarProps[] = [ ]; export default async function Layout({ children, params }: ITournamentLayout) { - const { data:getStagesData } = await getStages({ + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: getStagesData } = await getStages({ path: { abbreviation: params.tournamentId, }, }); - const { data:tournamentData } = await getTournamentByAbbreviation({ + const { data: tournamentData } = await getTournamentByAbbreviation({ path: { - abbreviation:params.tournamentId - } + abbreviation: params.tournamentId, + }, }); const getStateTypes = @@ -61,10 +77,7 @@ export default async function Layout({ children, params }: ITournamentLayout) { }; }) as INavbarProps[]; - if ( - tournamentData && - tournamentData?.tournamentType !== tournamentType.PARTICIPANT_VS - ) { + if (tournamentData && tournamentData?.tournamentType !== tournamentType.PARTICIPANT_VS) { tournamentNavbarRoutes.push({ name: "Teams", href: `/tournament/${params.tournamentId}/teams`, diff --git a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx index 81f1731..006b860 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx @@ -1,9 +1,16 @@ import React from "react"; import Link from "next/link"; +import { cookies } from "next/headers"; +import { + type BeatmapModificationResponseDto, + client, + getMappoolByStage, + type modification, + type StageResponseDto, +} from "../../../../../../../client"; import { stageTypeEnumToString } from "@/lib/helpers"; import { MappoolCard } from "@ui/molecules/Cards/MappoolCard"; import Section from "@ui/atoms/Section/Section"; -import { BeatmapModificationResponseDto, getMappoolByStage, StageResponseDto } from '../../../../../../../client' const SingleTournamentMappool = async ({ params, @@ -17,21 +24,28 @@ const SingleTournamentMappool = async ({ modification: BeatmapModificationResponseDto["modification"]; }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); let getMappoolByStageData, mappolStages; try { - const { data: getMappoolByStageData1 } = await getMappoolByStage( - { - path: { - abbreviation: params.tournamentId, - stageType: params.stageType, - } - } - ); + const { data: getMappoolByStageData1 } = await getMappoolByStage({ + path: { + abbreviation: params.tournamentId, + stageType: params.stageType, + }, + }); getMappoolByStageData = getMappoolByStageData1; mappolStages = getMappoolByStageData?.beatmapsModifications.filter( (stage) => !!stage.beatmaps, ); - } catch (error) { console.error(error); } @@ -71,7 +85,7 @@ const SingleTournamentMappool = async ({ { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data: getTournamentByAbbreviationData } = await getTournamentByAbbreviation({ path: { abbreviation: params.tournamentId, @@ -236,9 +250,9 @@ const SingleTournament = async ({ start: stage.startDate, end: stage.endDate, }, - stageEnum: stage.stageType, + stageEnum: stage.stageType as stageType, }} - mappool={mappool} + mappool={mappool as MappoolResponseDto} tournamentAbbreviation={params.tournamentId} />
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx b/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx index 0b3f191..533795f 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/registration/layout.tsx @@ -1,5 +1,17 @@ import React from "react"; +import { cookies } from "next/headers"; +import { client } from "../../../../../../client"; // todo po co to jest>? export default function MainPageLayout({ children }: { children: React.ReactNode }) { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); return
    {children}
    ; } diff --git a/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx index afba387..616aace 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/rules/page.tsx @@ -1,7 +1,8 @@ import React from "react"; import "react-quill/dist/quill.snow.css"; +import { cookies } from "next/headers"; +import { client, getTournamentByAbbreviation } from "../../../../../../client"; import Section from "@ui/atoms/Section/Section"; -import { getTournamentByAbbreviation } from '../../../../../../client' const styles = "[&_h1]:text-4xl " + @@ -22,13 +23,22 @@ const SingleTournamentRules = async ({ tournamentId: string; }; }) => { - const { data:tournament } = await getTournamentByAbbreviation({ + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: tournament } = await getTournamentByAbbreviation({ path: { abbreviation: params.tournamentId, }, }); - return (
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx index c33db04..db9d37c 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx @@ -1,6 +1,7 @@ import React from "react"; +import { cookies } from "next/headers"; +import { client, getStages } from "../../../../../../client"; import { ScheduleList } from "@ui/organisms/ScheduleList/ScheduleList"; -import { getStages } from '../../../../../../client' const SingleTournamentSchedule = async ({ params, @@ -9,10 +10,20 @@ const SingleTournamentSchedule = async ({ tournamentId: string; }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const { data } = await getStages({ path: { - abbreviation:params.tournamentId - } + abbreviation: params.tournamentId, + }, }); return (
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx index fe9c54d..29d6dad 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/staff/page.tsx @@ -1,7 +1,8 @@ import React from "react"; +import { cookies } from "next/headers"; +import { client, getStaffMembers } from "../../../../../../client"; import StaffMember from "@ui/organisms/StaffMember/StaffMember"; import Section from "@ui/atoms/Section/Section"; -import { getStaffMembers } from '../../../../../../client' const SingleTournamentStaff = async ({ params, @@ -10,7 +11,17 @@ const SingleTournamentStaff = async ({ tournamentId: string; }; }) => { - const { data:getStaffMembers1 } = await getStaffMembers({ + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: getStaffMembers1 } = await getStaffMembers({ path: { abbreviation: params.tournamentId, }, diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx index 3457587..c4cde67 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx @@ -5,7 +5,7 @@ import { Input } from "@ui/atoms/Forms/Input/Input"; import { Button } from "@ui/atoms/Button/Button"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; import { updateTeamSchema } from "@/formSchemas/updateTeamSchema"; -import { updateTeam } from "@/actions/public/createTeamAction"; +import { updateTeamAction } from "@/actions/public/createTeamAction"; export const ChangeTeamNameForm = ({ team: { teamId, teamName, logoUrl, tournamentAbbreviation }, @@ -20,7 +20,7 @@ export const ChangeTeamNameForm = ({ const [stateChangeTeamProperties, changeTeamPropertiesFormAction] = useTypeSafeFormState( updateTeamSchema, async (data) => { - const updateTeamResponse = await updateTeam(data); + const updateTeamResponse = await updateTeamAction(data); if (!updateTeamResponse.status) { return toast.error(updateTeamResponse.errorMessage, { duration: 3000, diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx index e5d98d3..8b0c5df 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx @@ -4,7 +4,7 @@ import { toast } from "sonner"; import { Input } from "@ui/atoms/Forms/Input/Input"; import { Button } from "@ui/atoms/Button/Button"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; -import { inviteToTeam } from "@/actions/public/createTeamAction"; +import { inviteToTeamAction } from "@/actions/public/createTeamAction"; import { inviteToTeamSchema } from "@/formSchemas/inviteToTeamSchema"; import { resetFormValues } from "@/lib/helpers"; @@ -20,7 +20,7 @@ export const InvitePlayerToTeamButton = ({ const [stateInviteToTeam, inviteToTeamFormAction] = useTypeSafeFormState( inviteToTeamSchema, async (data) => { - const inviteToTeamResponse = await inviteToTeam(data); + const inviteToTeamResponse = await inviteToTeamAction(data); if (!inviteToTeamResponse.status) { return toast.error(inviteToTeamResponse.errorMessage, { duration: 3000, diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx index 9a0d1f5..763679d 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx @@ -1,37 +1,54 @@ import React from "react"; import Image from "next/image"; import { redirect } from "next/navigation"; -import { TeamService } from "../../../../../../../generated"; +import { cookies } from "next/headers"; +import { + client, + deleteParticipantFromTeam, + disbandTeam, + getTeamsById, +} from "../../../../../../../client"; import Section from "@ui/atoms/Section/Section"; import { Button } from "@ui/atoms/Button/Button"; -import { executeFetch } from "@/lib/executeFetch"; import { getUser } from "@/actions/public/getUserAction"; import { ChangeTeamNameForm } from "@/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm"; import { InvitePlayerToTeamButton } from "@/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; const TeamPage = async ({ params: { tournamentId, teamId }, }: { params: { tournamentId: string; teamId: string }; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const userData = await getUser(); - const getTeam = await executeFetch(TeamService.getTeamsById(tournamentId, teamId)); + const { data: getTeam } = await getTeamsById({ + path: { + abbreviation: tournamentId, + teamId: teamId, + }, + }); - if (!getTeam.status) { - return
    {getTeam.errorMessage}
    ; - } - - const isCaptain = getTeam.response.captain.user.id === userData?.id; + const isCaptain = getTeam?.captain.user.id === userData?.id; return (
    - {getTeam.response.logoUrl && ( + {getTeam?.logoUrl && (
    team logo )}
    -

    {getTeam.response.name}

    -

    Status: {getTeam.response.status}

    +

    {getTeam?.name}

    +

    Status: {getTeam?.status}

    {isCaptain && (
    { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: disbandTeamResponse } = await disbandTeam({ + path: { + abbreviation: tournamentId, + teamId, + }, + }); - const disbandTeamResponse = await executeFetch( - TeamService.disbandTeam(tournamentId, teamId), - [ - `/tournament/${tournamentId}/teams`, - `/tournament/${tournamentId}`, - "/", - ], - ); - - if (disbandTeamResponse.status) { + await multipleRevalidatePaths([ + `/tournament/${tournamentId}/teams`, + `/tournament/${tournamentId}`, + "/", + ]); + if (disbandTeamResponse) { redirect(`/tournament/${tournamentId}/teams`); } }} @@ -80,9 +109,9 @@ const TeamPage = async ({
    @@ -90,7 +119,7 @@ const TeamPage = async ({
    @@ -109,7 +138,7 @@ const TeamPage = async ({ - {getTeam.response.participants.map((participant) => ( + {getTeam?.participants.map((participant) => (
    @@ -139,19 +168,29 @@ const TeamPage = async ({ } action={async (_e) => { "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await deleteParticipantFromTeam({ + path: { + teamId: getTeam?.id || "", + osuId: "" + participant.user.osuId, + abbreviation: tournamentId, + }, + }); - await executeFetch( - TeamService.deleteParticipantFromTeam( - tournamentId, - teamId, - "" + participant.user.osuId, - ), - [ - "/tournament/[tournamentId]/teams", - "/tournament", - "/", - ], - ); + await multipleRevalidatePaths([ + `/tournament/${tournamentId}/teams`, + `/tournament/${tournamentId}`, + "/", + ]); }} >
    - {getTeams.response.map((team) => ( + {getTeams?.map((team) => ( ))}
    diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 586b169..748fe09 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,8 @@ import "./globals.css"; import type { Metadata } from "next"; import { Montserrat } from "@next/font/google"; import { Toaster } from "sonner"; +import { cookies } from "next/headers"; +import { client } from "../../client"; export const metadata: Metadata = { title: "AimCup", @@ -13,7 +15,23 @@ const monserrat = Montserrat({ subsets: ["latin"], }); +// configure internal service client +client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests +}); + export default async function RootLayout({ children }: { children: React.ReactNode }) { + client.interceptors.request.use((req) => { + const cookie = cookies().get("JWT")?.value; + console.log(cookie, "cookiecookiecookiecookiecookiecookiecookiecookiecookiecookie"); + if (cookie) { + req.headers.set("Cookie", `token=${cookie}`); + } + return req; + }); + return ( { switch (stageType) { @@ -63,10 +62,3 @@ export const resetFormValues = >({ } }); }; - -export const multipleRevalidatePaths = (paths: string[]) => { - "use server"; - paths.forEach((path) => { - revalidatePath(path); - }); -}; diff --git a/src/lib/multipleRevalidatePaths.ts b/src/lib/multipleRevalidatePaths.ts new file mode 100644 index 0000000..490a29f --- /dev/null +++ b/src/lib/multipleRevalidatePaths.ts @@ -0,0 +1,9 @@ +"use server"; +import { revalidatePath } from "next/cache"; + +export const multipleRevalidatePaths = async (paths: string[]) => { + "use server"; + paths.forEach((path) => { + revalidatePath(path); + }); +}; diff --git a/src/lib/session.ts b/src/lib/session.ts index 7ad5076..1faa046 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,40 +1,40 @@ import "server-only"; -import { SignJWT, jwtVerify } from "jose"; +// import { SignJWT, jwtVerify } from "jose"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; -const secretKey = process.env.SECRET_LOGIN_KEY; -const key = new TextEncoder().encode(secretKey); +// const secretKey = process.env.SECRET_LOGIN_KEY; +// const key = new TextEncoder().encode(secretKey); +// +// type SessionPayload = { +// token: string | number; +// expiresAt: Date; +// }; -type SessionPayload = { - token: string | number; - expiresAt: Date; -}; - -export async function encrypt(payload: SessionPayload) { - return new SignJWT(payload) - .setProtectedHeader({ alg: "HS256" }) - .setIssuedAt() - .setExpirationTime("7d") - .sign(key); -} - -export async function decrypt(session: string | undefined = "") { - try { - const { payload } = await jwtVerify(session, key, { - algorithms: ["HS256"], - }); - return payload; - } catch (error) { - return null; - } -} +// export async function encrypt(payload: SessionPayload) { +// return new SignJWT(payload) +// .setProtectedHeader({ alg: "HS256" }) +// .setIssuedAt() +// .setExpirationTime("7d") +// .sign(key); +// } +// +// export async function decrypt(session: string | undefined = "") { +// try { +// const { payload } = await jwtVerify(session, key, { +// algorithms: ["HS256"], +// }); +// return payload; +// } catch (error) { +// return null; +// } +// } export async function createSession(token: string, redirectUrl: string) { const expiresAt = new Date(Date.now() + 86400000); - const session = await encrypt({ token, expiresAt }); + // const session = await encrypt({ token, expiresAt }); - cookies().set("JWT", session, { + cookies().set("JWT", token, { httpOnly: true, secure: true, expires: expiresAt, @@ -47,20 +47,20 @@ export async function createSession(token: string, redirectUrl: string) { export async function verifySession() { const cookie = cookies().get("JWT")?.value; - const session = await decrypt(cookie); + // const session = await decrypt(cookie); - if (!session?.token) { + if (!cookie) { return { isAuth: false, token: null }; } - return { isAuth: true, token: session.token }; + return { isAuth: true, token: cookie }; } export async function updateSession() { const session = cookies().get("JWT")?.value; - const payload = await decrypt(session); + // const payload = await decrypt(session); - if (!session || !payload) { + if (!session) { return null; } diff --git a/src/middleware.ts b/src/middleware.ts index 924e00e..99e3399 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,7 +1,5 @@ import { type NextRequest, NextResponse } from "next/server"; import { cookies } from "next/headers"; -import { client } from "../client"; -import { decrypt } from "@/lib/session"; // 1. Specify protected and public routes const protectedRoutes = ["/dashboard", "/account"]; @@ -15,24 +13,14 @@ export default async function middleware(req: NextRequest) { // 3. Decrypt the session from the cookie const cookie = cookies().get("JWT")?.value; - const session = await decrypt(cookie); - - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Authorization: `Bearer ${session?.token as string}`, - }, - }); + // const session = await decrypt(cookie); // 4. Redirect - if (isProtectedRoute && !session?.token) { + if (isProtectedRoute && !cookie) { return NextResponse.redirect(new URL("/register", req.nextUrl)); } - if (isPublicRoute && session?.token && !req.nextUrl.pathname.startsWith("/")) { + if (isPublicRoute && cookie && !req.nextUrl.pathname.startsWith("/")) { return NextResponse.redirect(new URL("/", req.nextUrl)); } diff --git a/src/ui/molecules/LoginAvatar/LoginAvatar.tsx b/src/ui/molecules/LoginAvatar/LoginAvatar.tsx index 724eba5..d0cf538 100644 --- a/src/ui/molecules/LoginAvatar/LoginAvatar.tsx +++ b/src/ui/molecules/LoginAvatar/LoginAvatar.tsx @@ -1,13 +1,24 @@ import React from "react"; import Link from "next/link"; import { redirect } from "next/navigation"; -import { verify } from "../../../../client"; +import { cookies } from "next/headers"; +import { client, verify } from "../../../../client"; import { Button } from "@ui/atoms/Button/Button"; import { getUser } from "@/actions/public/getUserAction"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; import { LogoutButton } from "@ui/atoms/LogoutButton/LogoutButton"; export const LoginAvatar = async () => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const userData = await getUser(); if (!userData?.id) { const redirectUri = encodeURIComponent(process.env.NEXT_PUBLIC_URL || "https://aimcup.xyz"); diff --git a/src/ui/molecules/RegisterToTournamentButton/RegisterToTournamentButton.tsx b/src/ui/molecules/RegisterToTournamentButton/RegisterToTournamentButton.tsx index 0b31b85..7642467 100644 --- a/src/ui/molecules/RegisterToTournamentButton/RegisterToTournamentButton.tsx +++ b/src/ui/molecules/RegisterToTournamentButton/RegisterToTournamentButton.tsx @@ -1,7 +1,7 @@ import React from "react"; -import { ParticipantService } from "../../../../generated"; +import { registerParticipant } from "../../../../client"; import { Button } from "@ui/atoms/Button/Button"; -import { executeFetch } from "@/lib/executeFetch"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; interface RegisterToTournamentButtonProps { tournamentId: string; @@ -23,7 +23,12 @@ const RegisterToTournamentButton = ({ { "use server"; - await executeFetch(ParticipantService.registerParticipant(tournamentId), [ + await registerParticipant({ + path: { + abbreviation: tournamentId, + }, + }); + await multipleRevalidatePaths([ "/", "/dashboard", "/tournament/[tournamentId]", diff --git a/src/ui/organisms/MappoolStages/MappoolStages.tsx b/src/ui/organisms/MappoolStages/MappoolStages.tsx index 924d1cf..3bbd2b3 100644 --- a/src/ui/organisms/MappoolStages/MappoolStages.tsx +++ b/src/ui/organisms/MappoolStages/MappoolStages.tsx @@ -1,6 +1,7 @@ import React from "react"; import { format } from "date-fns"; import type { MappoolResponseDto, StageResponseDto } from "../../../../generated"; +import { type stageType } from "../../../../client"; import { TournamentCard } from "@ui/molecules/Cards/TournamentCard"; import { stageTypeEnumToString } from "@/lib/helpers"; @@ -26,7 +27,7 @@ export const MappoolStages = (props: IMappoolStagesProps) => { return ( { const scheduleListContent = scheduleList?.map((stage) => { diff --git a/src/ui/organisms/TournamentList/TournamentList.tsx b/src/ui/organisms/TournamentList/TournamentList.tsx index de68c2e..1a6fada 100644 --- a/src/ui/organisms/TournamentList/TournamentList.tsx +++ b/src/ui/organisms/TournamentList/TournamentList.tsx @@ -1,7 +1,13 @@ import React from "react"; import { type EmblaOptionsType } from "embla-carousel"; import { format } from "date-fns"; -import { getTournaments, getUserTournaments, type TournamentResponseDto } from "../../../../client"; +import { cookies } from "next/headers"; +import { + client, + getTournaments, + getUserTournaments, + type TournamentResponseDto, +} from "../../../../client"; import { Carousel } from "@ui/organisms/Carousel/Carousel"; import { TournamentCard } from "@ui/molecules/Cards/TournamentCard"; @@ -15,6 +21,16 @@ export const TournamentList = async ({ /** Get all tournaments associated with the active user */ userTournaments?: boolean; }) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); let tournamentData: TournamentResponseDto[] | undefined; if (userTournaments) { diff --git a/src/ui/organisms/UserInformation/UserInformation.tsx b/src/ui/organisms/UserInformation/UserInformation.tsx index 12ca112..4444c9f 100644 --- a/src/ui/organisms/UserInformation/UserInformation.tsx +++ b/src/ui/organisms/UserInformation/UserInformation.tsx @@ -3,11 +3,23 @@ import { ImEarth } from "react-icons/im"; import { FaDiscord } from "react-icons/fa"; import { RiBarChartFill } from "react-icons/ri"; import Image from "next/image"; +import { cookies } from "next/headers"; +import { client } from "../../../../client"; import Section from "@ui/atoms/Section/Section"; import { getUser } from "@/actions/public/getUserAction"; import { LogoutButton } from "@ui/atoms/LogoutButton/LogoutButton"; export const UserInformation = async () => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); const userData = await getUser(); return ( diff --git a/src/ui/organisms/UserInformation/UserTeams.tsx b/src/ui/organisms/UserInformation/UserTeams.tsx index 8805e5e..e98d3fe 100644 --- a/src/ui/organisms/UserInformation/UserTeams.tsx +++ b/src/ui/organisms/UserInformation/UserTeams.tsx @@ -1,13 +1,23 @@ -import { UserService } from "../../../../generated"; +import { cookies } from "next/headers"; +import { client, getUserTeams } from "../../../../client"; import { TeamCard } from "@ui/molecules/Cards/TeamCard"; -import { executeFetch } from "@/lib/executeFetch"; const UserTeams = async () => { - const userTeams = await executeFetch(UserService.getUserTeams()); - if (!userTeams.status) { - return
    {userTeams.errorMessage}
    ; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data: userTeams } = await getUserTeams(); + if (!userTeams) { + return
    No teams found
    ; } - return userTeams.response.map((team) => ( + return userTeams.map((team) => ( )); }; From abb6d76d4de44169efbe084594b9e40053b121df Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 16 Aug 2024 15:43:17 +0200 Subject: [PATCH 10/37] fix: fixed requests --- .github/workflows/deploy.next.yml | 2 +- .github/workflows/deploy.production.yml | 2 +- src/app/(tournament)/tournament/[tournamentId]/page.tsx | 2 +- src/ui/molecules/Cards/MappoolCard.tsx | 2 +- src/ui/molecules/Cards/TeamCard.tsx | 2 +- src/ui/organisms/MappoolStages/MappoolStages.tsx | 3 +-- src/ui/organisms/StaffMember/StaffMember.tsx | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy.next.yml b/.github/workflows/deploy.next.yml index 6e5a11d..eb7ea47 100644 --- a/.github/workflows/deploy.next.yml +++ b/.github/workflows/deploy.next.yml @@ -27,7 +27,7 @@ jobs: - name: Fetch OpenAPI schema run: npm run open:api - name: Generate API client - run: npm run open:generate + run: npm run openapi-ts - name: Build and deploy to Next environment run: docker-compose -f docker-compose.next.yml up -d --build env: diff --git a/.github/workflows/deploy.production.yml b/.github/workflows/deploy.production.yml index 6917beb..96aebe0 100644 --- a/.github/workflows/deploy.production.yml +++ b/.github/workflows/deploy.production.yml @@ -19,7 +19,7 @@ jobs: - name: Fetch OpenAPI schema run: npm run open:api - name: Generate API client - run: npm run open:generate + run: npm run openapi-ts - name: Build and deploy to Next environment run: docker-compose -f docker-compose.yml up -d --build env: diff --git a/src/app/(tournament)/tournament/[tournamentId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/page.tsx index bf24ac6..1b5863c 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/page.tsx @@ -13,12 +13,12 @@ import { getStages, getTeamsByTournament, getTournamentByAbbreviation, + MappoolResponseDto, type StageResponseDto, type stageType, type TeamResponseDto, type tournamentType, } from "../../../../../client"; -import type { MappoolResponseDto } from "../../../../../generated"; import { TeamCard } from "@ui/molecules/Cards/TeamCard"; import { Socials } from "@ui/organisms/Socials/Socials"; import RegisterToTournamentButton from "@ui/molecules/RegisterToTournamentButton/RegisterToTournamentButton"; diff --git a/src/ui/molecules/Cards/MappoolCard.tsx b/src/ui/molecules/Cards/MappoolCard.tsx index 04ba374..487112f 100644 --- a/src/ui/molecules/Cards/MappoolCard.tsx +++ b/src/ui/molecules/Cards/MappoolCard.tsx @@ -3,8 +3,8 @@ import Image from "next/image"; import { IoTime } from "react-icons/io5"; import { IoIosStar } from "react-icons/io"; import { PiMetronomeFill } from "react-icons/pi"; -import { type BeatmapModificationResponseDto } from "../../../../generated"; import { TextBox } from "@ui/atoms/TextBox/TextBox"; +import { BeatmapModificationResponseDto } from "../../../../client"; export const MappoolCard = ({ title, diff --git a/src/ui/molecules/Cards/TeamCard.tsx b/src/ui/molecules/Cards/TeamCard.tsx index 5563027..f061bf3 100644 --- a/src/ui/molecules/Cards/TeamCard.tsx +++ b/src/ui/molecules/Cards/TeamCard.tsx @@ -1,8 +1,8 @@ import React from "react"; import { PiCrownSimpleFill } from "react-icons/pi"; import Link from "next/link"; -import { type TeamResponseDto } from "../../../../generated"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; +import { TeamResponseDto } from "../../../../client"; export const TeamCard = ({ team, diff --git a/src/ui/organisms/MappoolStages/MappoolStages.tsx b/src/ui/organisms/MappoolStages/MappoolStages.tsx index 3bbd2b3..a58c034 100644 --- a/src/ui/organisms/MappoolStages/MappoolStages.tsx +++ b/src/ui/organisms/MappoolStages/MappoolStages.tsx @@ -1,7 +1,6 @@ import React from "react"; import { format } from "date-fns"; -import type { MappoolResponseDto, StageResponseDto } from "../../../../generated"; -import { type stageType } from "../../../../client"; +import { MappoolResponseDto, StageResponseDto, type stageType } from "../../../../client"; import { TournamentCard } from "@ui/molecules/Cards/TournamentCard"; import { stageTypeEnumToString } from "@/lib/helpers"; diff --git a/src/ui/organisms/StaffMember/StaffMember.tsx b/src/ui/organisms/StaffMember/StaffMember.tsx index 9597d63..da21bd0 100644 --- a/src/ui/organisms/StaffMember/StaffMember.tsx +++ b/src/ui/organisms/StaffMember/StaffMember.tsx @@ -1,7 +1,7 @@ import React from "react"; import Link from "next/link"; -import { type StaffMemberResponseDto } from "../../../../generated"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; +import { StaffMemberResponseDto } from "../../../../client"; type StaffMemberProps = { staffMember: StaffMemberResponseDto; From 709c07e90111af846733e1a929a9648adee29fc8 Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 16 Aug 2024 16:10:59 +0200 Subject: [PATCH 11/37] fix: fixed requests --- src/app/(tournament)/tournament/[tournamentId]/page.tsx | 3 +-- src/ui/molecules/Cards/MappoolCard.tsx | 2 +- src/ui/molecules/Cards/TeamCard.tsx | 2 +- src/ui/organisms/MappoolStages/MappoolStages.tsx | 2 +- src/ui/organisms/StaffMember/StaffMember.tsx | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/page.tsx index 1b5863c..2f314f2 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/page.tsx @@ -13,7 +13,6 @@ import { getStages, getTeamsByTournament, getTournamentByAbbreviation, - MappoolResponseDto, type StageResponseDto, type stageType, type TeamResponseDto, @@ -252,7 +251,7 @@ const SingleTournament = async ({ }, stageEnum: stage.stageType as stageType, }} - mappool={mappool as MappoolResponseDto} + mappool={mappool} tournamentAbbreviation={params.tournamentId} />
    diff --git a/src/ui/molecules/Cards/MappoolCard.tsx b/src/ui/molecules/Cards/MappoolCard.tsx index 487112f..5c13147 100644 --- a/src/ui/molecules/Cards/MappoolCard.tsx +++ b/src/ui/molecules/Cards/MappoolCard.tsx @@ -3,8 +3,8 @@ import Image from "next/image"; import { IoTime } from "react-icons/io5"; import { IoIosStar } from "react-icons/io"; import { PiMetronomeFill } from "react-icons/pi"; +import { type BeatmapModificationResponseDto } from "../../../../client"; import { TextBox } from "@ui/atoms/TextBox/TextBox"; -import { BeatmapModificationResponseDto } from "../../../../client"; export const MappoolCard = ({ title, diff --git a/src/ui/molecules/Cards/TeamCard.tsx b/src/ui/molecules/Cards/TeamCard.tsx index f061bf3..5fffc0c 100644 --- a/src/ui/molecules/Cards/TeamCard.tsx +++ b/src/ui/molecules/Cards/TeamCard.tsx @@ -1,8 +1,8 @@ import React from "react"; import { PiCrownSimpleFill } from "react-icons/pi"; import Link from "next/link"; +import { type TeamResponseDto } from "../../../../client"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; -import { TeamResponseDto } from "../../../../client"; export const TeamCard = ({ team, diff --git a/src/ui/organisms/MappoolStages/MappoolStages.tsx b/src/ui/organisms/MappoolStages/MappoolStages.tsx index a58c034..91465c9 100644 --- a/src/ui/organisms/MappoolStages/MappoolStages.tsx +++ b/src/ui/organisms/MappoolStages/MappoolStages.tsx @@ -1,6 +1,6 @@ import React from "react"; import { format } from "date-fns"; -import { MappoolResponseDto, StageResponseDto, type stageType } from "../../../../client"; +import { type MappoolResponseDto, type StageResponseDto, type stageType } from "../../../../client"; import { TournamentCard } from "@ui/molecules/Cards/TournamentCard"; import { stageTypeEnumToString } from "@/lib/helpers"; diff --git a/src/ui/organisms/StaffMember/StaffMember.tsx b/src/ui/organisms/StaffMember/StaffMember.tsx index da21bd0..6ea9f74 100644 --- a/src/ui/organisms/StaffMember/StaffMember.tsx +++ b/src/ui/organisms/StaffMember/StaffMember.tsx @@ -1,7 +1,7 @@ import React from "react"; import Link from "next/link"; +import { type StaffMemberResponseDto } from "../../../../client"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; -import { StaffMemberResponseDto } from "../../../../client"; type StaffMemberProps = { staffMember: StaffMemberResponseDto; From 8e9ee59a6e2e1e40c4ea6ac03def71a50b97ce64 Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 16 Aug 2024 17:23:05 +0200 Subject: [PATCH 12/37] fix: fixed endpoint path variable name --- .../tournament/[tournamentId]/teams/[teamId]/page.tsx | 2 +- src/app/layout.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx index 763679d..c1f523b 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx @@ -181,7 +181,7 @@ const TeamPage = async ({ await deleteParticipantFromTeam({ path: { teamId: getTeam?.id || "", - osuId: "" + participant.user.osuId, + participantId: "" + participant.id, abbreviation: tournamentId, }, }); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 748fe09..ba03808 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -25,7 +25,6 @@ client.setConfig({ export default async function RootLayout({ children }: { children: React.ReactNode }) { client.interceptors.request.use((req) => { const cookie = cookies().get("JWT")?.value; - console.log(cookie, "cookiecookiecookiecookiecookiecookiecookiecookiecookiecookie"); if (cookie) { req.headers.set("Cookie", `token=${cookie}`); } From 8b64c21d7f10c43655624d6dfadf587c1698655b Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 16 Aug 2024 18:49:29 +0200 Subject: [PATCH 13/37] fix: fixed display issues --- .../tournament/[tournamentId]/layout.tsx | 6 +++- .../tournament/[tournamentId]/teams/page.tsx | 14 ++++++-- src/ui/molecules/Cards/TeamCard.tsx | 4 ++- .../organisms/ScheduleList/ScheduleList.tsx | 34 +++++++++++-------- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/layout.tsx b/src/app/(tournament)/tournament/[tournamentId]/layout.tsx index 000feb5..e77b214 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/layout.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/layout.tsx @@ -57,7 +57,11 @@ export default async function Layout({ children, params }: ITournamentLayout) { const getStateTypes = getStagesData && - getStagesData.filter((stage) => !!stage.mappool).map((stage) => stage.stageType); + getStagesData + .filter((stage) => !!stage.mappool) + .filter((stage) => stage.stageType !== "REGISTRATION") + .filter((stage) => stage.stageType !== "SCREENING") + .map((stage) => stage.stageType); const tournamentNavbarRoutes: INavbarProps[] = navbarRoutes.map((item) => { if (item.name === "Mappool") { diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/page.tsx index ea4184f..5101283 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/page.tsx @@ -54,9 +54,17 @@ const SingleTournamentTeams = async ({
    - {getTeams?.map((team) => ( - - ))} + {getTeams + ?.sort((a, b) => { + // sort array by averagePerformancePoints, descending + return ( + (b?.averagePerformancePoints || 0) - + (a?.averagePerformancePoints || 0) + ); + }) + .map((team) => ( + + ))}
    diff --git a/src/ui/molecules/Cards/TeamCard.tsx b/src/ui/molecules/Cards/TeamCard.tsx index 5fffc0c..0d87884 100644 --- a/src/ui/molecules/Cards/TeamCard.tsx +++ b/src/ui/molecules/Cards/TeamCard.tsx @@ -23,7 +23,9 @@ export const TeamCard = ({

    {team?.name}

    -

    x̄ PP: {team?.averagePerformancePoints}

    +

    + Avg. PP: {team?.averagePerformancePoints?.toFixed(2)} +

    {team?.participants?.map((participant) => (
    diff --git a/src/ui/organisms/ScheduleList/ScheduleList.tsx b/src/ui/organisms/ScheduleList/ScheduleList.tsx index 9bac714..6624398 100644 --- a/src/ui/organisms/ScheduleList/ScheduleList.tsx +++ b/src/ui/organisms/ScheduleList/ScheduleList.tsx @@ -1,23 +1,27 @@ import React from "react"; import { format } from "date-fns"; -import { type StageResponseDto } from '../../../../client' +import { type StageResponseDto } from "../../../../client"; import { stageTypeEnumToString } from "@/lib/helpers"; export const ScheduleList = ({ scheduleList }: { scheduleList: StageResponseDto[] }) => { - const scheduleListContent = scheduleList?.map((stage) => { - return ( -
    - {stageTypeEnumToString(stage.stageType)} - - - {format(new Date(stage.startDate), "dd/MM/yyyy")} -{" "} - {format(new Date(stage.endDate), "dd/MM/yyyy")} - -
    - ); - }) ?? <>No schedule; + const scheduleListContent = scheduleList + ?.sort((a, b) => { + return new Date(a.startDate).getTime() - new Date(b.startDate).getTime(); + }) + .map((stage) => { + return ( +
    + {stageTypeEnumToString(stage.stageType)} + + + {format(new Date(stage.startDate), "dd/MM/yyyy")} -{" "} + {format(new Date(stage.endDate), "dd/MM/yyyy")} + +
    + ); + }) ?? <>No schedule; return scheduleListContent; }; From ad546ac057f33b52ecc3c704a15a602bd7ec1ac0 Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 13 Sep 2024 19:12:36 +0200 Subject: [PATCH 14/37] feat: added qualification rooms --- .../(main-page)/(withAuth)/account/page.tsx | 16 +- .../qualification-rooms/page.tsx | 86 +++++--- .../tournament/[tournamentId]/layout.tsx | 17 ++ .../tournament/[tournamentId]/page.tsx | 29 +++ .../qualification-rooms/page.tsx | 196 ++++++++++++++++++ src/models/QualificationRoom.ts | 24 +++ .../UserInformation/UserInformation.tsx | 11 +- 7 files changed, 332 insertions(+), 47 deletions(-) create mode 100644 src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx create mode 100644 src/models/QualificationRoom.ts diff --git a/src/app/(main-page)/(withAuth)/account/page.tsx b/src/app/(main-page)/(withAuth)/account/page.tsx index 04b8ba6..c9ef8a1 100644 --- a/src/app/(main-page)/(withAuth)/account/page.tsx +++ b/src/app/(main-page)/(withAuth)/account/page.tsx @@ -7,8 +7,10 @@ import UserTeams from "@ui/organisms/UserInformation/UserTeams"; const AccountPage = async () => { return ( -
    - +
    +
    + +
    }>
    @@ -20,11 +22,9 @@ const AccountPage = async () => {
    }> -
    -
    -

    My teams

    - -
    +
    +

    My teams

    +
    {/*}>*/} @@ -37,7 +37,7 @@ const AccountPage = async () => { {/*
    */} {/*
    */} {/**/} -
    +
    ); }; diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx index 581a945..adb2a41 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx @@ -11,35 +11,18 @@ import { getTeamsByTournament, getTournamentByAbbreviation, getTournamentStaffMember, - type ParticipantResponseDto, type QualificationRoomResponseDto, - signInQualificationRoom, - type StaffMemberResponseDto, - type TeamResponseDto, + signInOutQualificationRoom, tournamentType, } from "../../../../../../../client"; import { QualificationRoomModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal"; import { type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; import { getUser } from "@/actions/public/getUserAction"; import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; - -interface QualificationRoom extends QualificationRoomResponseDto { - id: string; - number: number; - isClosed: number; - maxSlots: number; - occupiedSlots: number; - staffMember: StaffMemberResponseDto; - startDate: string; -} - -interface TeamBasedQualificationRoom extends QualificationRoom { - teams: TeamResponseDto[]; -} - -interface ParticipantBasedQualificationRoom extends QualificationRoom { - participants: ParticipantResponseDto[]; -} +import { + type ParticipantBasedQualificationRoom, + type TeamBasedQualificationRoom, +} from "@/models/QualificationRoom"; const qualificationRooms = ( rooms: QualificationRoomResponseDto[], @@ -138,9 +121,6 @@ const QRoomsPage = async ({ return (
    - {` - brakujue sign out :) - `}

    Qualification rooms

    ( {room.number} - {format(new Date(room.startDate), "dd/MM/yyyy hh:mm")} - + {format(new Date(room.startDate), "dd/MM/yyyy HH:mm")} + {room.tournamentType === tournamentType.PARTICIPANT_VS ? ( room as ParticipantBasedQualificationRoom @@ -175,21 +155,58 @@ const QRoomsPage = async ({
    )) : (room as TeamBasedQualificationRoom).teams.map((team) => ( -
    - {team.name} +
    {team.name} + {team.name}
    ))} {room.staffMember ? ( userData?.id === room.staffMember.user?.id ? ( -
    Sign out:todo
    + { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInOutQualificationRoom({ + path: { + abbreviation: tournamentAbbreviation, + roomId: room.id, + }, + query: { + in: false, + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + ]); + }} + > + + ) : (
    @@ -219,11 +236,14 @@ const QRoomsPage = async ({ Cookie: `token=${cookie}`, }, }); - await signInQualificationRoom({ + await signInOutQualificationRoom({ path: { abbreviation: tournamentAbbreviation, roomId: room.id, }, + query: { + in: true, + }, }); await multipleRevalidatePaths([ "/", diff --git a/src/app/(tournament)/tournament/[tournamentId]/layout.tsx b/src/app/(tournament)/tournament/[tournamentId]/layout.tsx index e77b214..ce96c97 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/layout.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/layout.tsx @@ -4,6 +4,7 @@ import NextTopLoader from "nextjs-toploader"; import { cookies } from "next/headers"; import { client, + getQualificationRooms, getStages, getTournamentByAbbreviation, tournamentType, @@ -22,6 +23,7 @@ type ITournamentLayout = { const navbarRoutes: INavbarProps[] = [ { name: "Home", href: "/" }, { name: "Rules", href: "/rules" }, + { name: "Qualification rooms", href: "/qualification-rooms" }, { name: "Schedule", href: "/schedule" }, { name: "Mappool", @@ -55,6 +57,10 @@ export default async function Layout({ children, params }: ITournamentLayout) { }, }); + const { data: getQualificationRoomsData } = await getQualificationRooms({ + path: { abbreviation: params.tournamentId }, + }); + const getStateTypes = getStagesData && getStagesData @@ -64,6 +70,17 @@ export default async function Layout({ children, params }: ITournamentLayout) { .map((stage) => stage.stageType); const tournamentNavbarRoutes: INavbarProps[] = navbarRoutes.map((item) => { + if ( + getQualificationRoomsData?.length && + getQualificationRoomsData?.length > 0 && + item.name === "Qualification rooms" + ) { + return { + ...item, + href: `/tournament/${params.tournamentId}/qualification-rooms`, + }; + } + if (item.name === "Mappool") { return { ...item, diff --git a/src/app/(tournament)/tournament/[tournamentId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/page.tsx index 2f314f2..a70f353 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/page.tsx @@ -9,6 +9,7 @@ import { cookies } from "next/headers"; import { client, getMappoolsByTournament, + getQualificationRooms, getStaffMembers1, getStages, getTeamsByTournament, @@ -60,6 +61,10 @@ const SingleTournament = async ({ }, }); + const { data: getQualificationRoomsData } = await getQualificationRooms({ + path: { abbreviation: params.tournamentId }, + }); + let teams: TeamResponseDto[] = []; let teamSize = "1vs1"; // 1vs1 for participants if ( @@ -228,6 +233,30 @@ const SingleTournament = async ({
    + {getQualificationRoomsData?.length && getQualificationRoomsData?.length > 0 && ( +
    +
    + +

    + Qualification rooms +

    {" "} + + +
    +
    + )} {mappools && mappools.length !== 0 ? (
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx new file mode 100644 index 0000000..6fc9d98 --- /dev/null +++ b/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx @@ -0,0 +1,196 @@ +import React from "react"; +import { cookies } from "next/headers"; +import { format } from "date-fns"; +import Image from "next/image"; +import { + client, + getQualificationRooms, + getTeamsByTournament, + type QualificationRoomResponseDto, + signInQualificationRoomAsCaptain, + tournamentType, +} from "../../../../../../client"; +import { getUser } from "@/actions/public/getUserAction"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import Section from "@ui/atoms/Section/Section"; +import { + type ParticipantBasedQualificationRoom, + type TeamBasedQualificationRoom, +} from "@/models/QualificationRoom"; + +const qualificationRooms = ( + rooms: QualificationRoomResponseDto[], +): (TeamBasedQualificationRoom | ParticipantBasedQualificationRoom)[] => { + return rooms.map((room) => { + if (room.tournamentType !== tournamentType.PARTICIPANT_VS) { + return room as TeamBasedQualificationRoom; + } else { + return room as ParticipantBasedQualificationRoom; + } + }); +}; + +const QualificationRoomsPage = async ({ + params: { tournamentId }, +}: { + params: { + tournamentId: string; + }; +}) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const userData = await getUser(); + + const { data: getQualificationRoomsData } = await getQualificationRooms({ + path: { abbreviation: tournamentId }, + }); + + const { data: getTeamFromTournamentData } = await getTeamsByTournament({ + path: { abbreviation: tournamentId }, + }); + + const isUserCaptain = getTeamFromTournamentData?.some( + (team) => team.captain?.user.id === userData?.id, + ); + + const isTeamSignedIn = ( + currentRoom: TeamBasedQualificationRoom | ParticipantBasedQualificationRoom, + ) => { + return ( + currentRoom.tournamentType !== tournamentType.PARTICIPANT_VS && + (currentRoom as TeamBasedQualificationRoom).teams?.some((team) => + team.participants.some((participant) => participant.id === userData?.id), + ) + ); + }; + + return ( +
    +
    +

    Qualification rooms

    + +
    + + + + + + + + {isUserCaptain && } + + + + {qualificationRooms(getQualificationRoomsData || []).map((room) => ( + + + + + + {isUserCaptain && ( + + )} + + ))} + +
    NumberStart date timeRosterRefereeActions
    {room.number}{format(new Date(room.startDate), "dd/MM/yyyy HH:mm")} + {room.tournamentType === tournamentType.PARTICIPANT_VS + ? ( + room as ParticipantBasedQualificationRoom + ).participants.map((participant) => ( +
    + {participant.user.username} +
    + )) + : (room as TeamBasedQualificationRoom).teams.map( + (team) => ( +
    + {team.name} + {team.name} +
    + ), + )} +
    + {room.staffMember ? ( +
    +
    +
    + Avatar Tailwind CSS Component +
    +
    + {room.staffMember.user?.username} +
    + ) : ( + "-" + )} +
    + {isUserCaptain && !isTeamSignedIn(room) ? ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data } = + await signInQualificationRoomAsCaptain({ + path: { + abbreviation: tournamentId, + roomId: room.id, + }, + }); + + console.log(data, "awd"); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentId}/qualification-rooms`, + `/tournament/${tournamentId}/qualification-rooms`, + ]); + }} + > + +
    + ) : ( + "-" + )} +
    +
    +
    +
    + ); +}; + +export default QualificationRoomsPage; diff --git a/src/models/QualificationRoom.ts b/src/models/QualificationRoom.ts new file mode 100644 index 0000000..71ea97b --- /dev/null +++ b/src/models/QualificationRoom.ts @@ -0,0 +1,24 @@ +import type { + ParticipantResponseDto, + QualificationRoomResponseDto, + StaffMemberResponseDto, + TeamResponseDto, +} from "../../client"; + +interface QualificationRoom extends QualificationRoomResponseDto { + id: string; + number: number; + isClosed: number; + maxSlots: number; + occupiedSlots: number; + staffMember: StaffMemberResponseDto; + startDate: string; +} + +export interface TeamBasedQualificationRoom extends QualificationRoom { + teams: TeamResponseDto[]; +} + +export interface ParticipantBasedQualificationRoom extends QualificationRoom { + participants: ParticipantResponseDto[]; +} diff --git a/src/ui/organisms/UserInformation/UserInformation.tsx b/src/ui/organisms/UserInformation/UserInformation.tsx index 4444c9f..6e3b988 100644 --- a/src/ui/organisms/UserInformation/UserInformation.tsx +++ b/src/ui/organisms/UserInformation/UserInformation.tsx @@ -5,7 +5,6 @@ import { RiBarChartFill } from "react-icons/ri"; import Image from "next/image"; import { cookies } from "next/headers"; import { client } from "../../../../client"; -import Section from "@ui/atoms/Section/Section"; import { getUser } from "@/actions/public/getUserAction"; import { LogoutButton } from "@ui/atoms/LogoutButton/LogoutButton"; @@ -23,7 +22,7 @@ export const UserInformation = async () => { const userData = await getUser(); return ( -
    +

    Welcome back,

    {userData?.username}

    @@ -45,16 +44,16 @@ export const UserInformation = async () => {

    - TODO + ---

    - TODO + ---

    - TODO + {userData?.discordId}

    -
    +
    ); }; From da709afc45b7ac6fbec44312fb19c06e1a680916 Mon Sep 17 00:00:00 2001 From: Patryk Date: Fri, 13 Sep 2024 21:08:08 +0200 Subject: [PATCH 15/37] fix: fixed max qualification room offset --- .../qualification-rooms/QualificationRoomModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal.tsx index 8f000f0..ad15cac 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/QualificationRoomModal.tsx @@ -154,7 +154,7 @@ export const QualificationRoomModal = ({ tournamentAbb, modalType }: IAddStaffMe name={"offset"} label={"Offset"} required={true} - max={24} + min={1} disabled={modalType.type === "edit"} errorMessage={ stateAddNewQRoom?.errors.offset && From a0aa6e9fdc333cbfbff4c339ff2a0c27b6c37685 Mon Sep 17 00:00:00 2001 From: Patryk Date: Sun, 15 Sep 2024 17:49:11 +0200 Subject: [PATCH 16/37] feat: added download button --- .../tournament/[tournamentId]/mappool/[stageType]/page.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx index 006b860..d4f1f64 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx @@ -11,6 +11,7 @@ import { import { stageTypeEnumToString } from "@/lib/helpers"; import { MappoolCard } from "@ui/molecules/Cards/MappoolCard"; import Section from "@ui/atoms/Section/Section"; +import { Button } from "@ui/atoms/Button/Button"; const SingleTournamentMappool = async ({ params, @@ -195,6 +196,11 @@ const SingleTournamentMappool = async ({

    Mappool

    + + {getMappoolByStageData?.downloadUrl && ( + + )} + {stageContent} From 69831874a7fad75d591e8d127082e280e46e90d0 Mon Sep 17 00:00:00 2001 From: Patryk Date: Sun, 15 Sep 2024 17:53:04 +0200 Subject: [PATCH 17/37] fix: fixed mappool --- .gitignore | 4 +++- src/actions/admin/adminBeatMapActions.ts | 8 +++++++- .../mappool/[stageType]/[mappoolId]/page.tsx | 2 +- .../qualification-rooms/page.tsx | 20 ++++++++++--------- .../qualification-rooms/page.tsx | 20 ++++++++++--------- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 27ff020..cd93a43 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ next-env.d.ts openapi.json /generated .idea -.env \ No newline at end of file +.env + +/client \ No newline at end of file diff --git a/src/actions/admin/adminBeatMapActions.ts b/src/actions/admin/adminBeatMapActions.ts index e85722a..ecbbfd6 100644 --- a/src/actions/admin/adminBeatMapActions.ts +++ b/src/actions/admin/adminBeatMapActions.ts @@ -2,6 +2,7 @@ import { cookies } from "next/headers"; import { addBeatmap, client, type modification } from "../../../client"; import { type AddBeatMapSchemaType } from "@/formSchemas/addBeatMapSchema"; +// import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; export async function addBeatMapAction(formData: AddBeatMapSchemaType) { "use server"; @@ -35,7 +36,12 @@ export async function addBeatMapAction(formData: AddBeatMapSchemaType) { }; } - // todo: ADD REVALIDATE_PATHS + // await multipleRevalidatePaths([ + // "/", + // `/dashboard/${formData.tournamentAbb}/mappool/${formData.mappoolId}`, + // `/tournament/${formData.tournamentAbb}/mappool/${formData.mappoolId}`, + // ]); + //todo: revalidate paths return { status: true as const, diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx index db292d1..d06784c 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx @@ -120,7 +120,7 @@ const StageTypePage = async ({ {beatmap.beatmapStatistics.ar.toFixed(2)} {beatmap.beatmapStatistics.hp.toFixed(2)} {beatmap.beatmapStatistics.starRating.toFixed(2)} - {beatmap.beatmapStatistics.bpm} + {beatmap.beatmapStatistics.bpm.toFixed(2)} {beatmap.beatmapStatistics.length} {beatmap.creator} diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx index adb2a41..ce66e22 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx @@ -27,13 +27,17 @@ import { const qualificationRooms = ( rooms: QualificationRoomResponseDto[], ): (TeamBasedQualificationRoom | ParticipantBasedQualificationRoom)[] => { - return rooms.map((room) => { - if (room.tournamentType !== tournamentType.PARTICIPANT_VS) { - return room as TeamBasedQualificationRoom; - } else { - return room as ParticipantBasedQualificationRoom; - } - }); + return rooms + .map((room) => { + if (room.tournamentType !== tournamentType.PARTICIPANT_VS) { + return room as TeamBasedQualificationRoom; + } else { + return room as ParticipantBasedQualificationRoom; + } + }) + .sort((a, b) => { + return a.startDate > b.startDate ? 1 : -1; + }); }; const QRoomsPage = async ({ @@ -133,7 +137,6 @@ const QRoomsPage = async ({ - @@ -143,7 +146,6 @@ const QRoomsPage = async ({ {qualificationRooms(getQualificationRoomsData || []).map((room) => ( -
    Number Start date time Roster Referee
    {room.number} {format(new Date(room.startDate), "dd/MM/yyyy HH:mm")} {room.tournamentType === tournamentType.PARTICIPANT_VS diff --git a/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx index 6fc9d98..506f95b 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx @@ -21,13 +21,17 @@ import { const qualificationRooms = ( rooms: QualificationRoomResponseDto[], ): (TeamBasedQualificationRoom | ParticipantBasedQualificationRoom)[] => { - return rooms.map((room) => { - if (room.tournamentType !== tournamentType.PARTICIPANT_VS) { - return room as TeamBasedQualificationRoom; - } else { - return room as ParticipantBasedQualificationRoom; - } - }); + return rooms + .map((room) => { + if (room.tournamentType !== tournamentType.PARTICIPANT_VS) { + return room as TeamBasedQualificationRoom; + } else { + return room as ParticipantBasedQualificationRoom; + } + }) + .sort((a, b) => { + return a.startDate > b.startDate ? 1 : -1; + }); }; const QualificationRoomsPage = async ({ @@ -81,7 +85,6 @@ const QualificationRoomsPage = async ({ - @@ -91,7 +94,6 @@ const QualificationRoomsPage = async ({ {qualificationRooms(getQualificationRoomsData || []).map((room) => ( - From 026c7922467d2478c0b6dfd44754c3227a235555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 24 Sep 2024 22:53:20 +0200 Subject: [PATCH 31/37] fix: added captain's discord username in team's page --- .../[tournamentId]/schedule/page.tsx | 21 +++++++++++++++++-- .../[tournamentId]/teams/[teamId]/page.tsx | 5 ++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx index 85011d2..04916e4 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx @@ -1,6 +1,7 @@ import React from "react"; import { cookies } from "next/headers"; import { format } from "date-fns"; +import Link from "next/link"; import { client, getMatches } from "../../../../../../client"; import Section from "@ui/atoms/Section/Section"; @@ -56,8 +57,24 @@ const SingleTournamentSchedule = async ({ {format(new Date(match.startDate), "dd/MM/yyyy HH:mm")} - - + + + @@ -98,6 +102,18 @@ const SingleTournamentSchedule = async ({ ))} + ))} diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx index c4cde67..6fc7526 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx @@ -1,6 +1,8 @@ "use client"; -import React from "react"; +import React, { useState } from "react"; import { toast } from "sonner"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; import { Input } from "@ui/atoms/Forms/Input/Input"; import { Button } from "@ui/atoms/Button/Button"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; @@ -9,6 +11,7 @@ import { updateTeamAction } from "@/actions/public/createTeamAction"; export const ChangeTeamNameForm = ({ team: { teamId, teamName, logoUrl, tournamentAbbreviation }, + isRegistrationStage, }: { team: { tournamentAbbreviation: string; @@ -16,19 +19,122 @@ export const ChangeTeamNameForm = ({ teamName: string; logoUrl: string; }; + isRegistrationStage: boolean; }) => { + const router = useRouter(); + const [selectedLogo, setSelectedLogo] = useState(null); + const [logoPreview, setLogoPreview] = useState(null); + const [logoError, setLogoError] = useState(null); + + const validateLogo = (file: File): Promise => { + return new Promise((resolve) => { + // Check file size (max 2MB) + if (file.size > 2 * 1024 * 1024) { + resolve("Image size cannot exceed 2MB"); + return; + } + + // Check file extension + const allowedExtensions = [".jpg", ".jpeg", ".png"]; + const fileName = file.name.toLowerCase(); + const hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext)); + if (!hasValidExtension) { + resolve("Image must be in format: jpg, jpeg, or png"); + return; + } + + // Check image dimensions + const img = new window.Image(); + const objectUrl = URL.createObjectURL(file); + img.onload = () => { + URL.revokeObjectURL(objectUrl); + if (img.width > 50 || img.height > 50) { + resolve("Image dimensions cannot exceed 50x50 pixels"); + } else { + resolve(null); + } + }; + img.onerror = () => { + URL.revokeObjectURL(objectUrl); + resolve("Invalid image file"); + }; + img.src = objectUrl; + }); + }; + + const handleLogoChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) { + setSelectedLogo(null); + setLogoPreview(null); + setLogoError(null); + return; + } + + setLogoError(null); + + // Validate file + const error = await validateLogo(file); + if (error) { + setLogoError(error); + setSelectedLogo(null); + setLogoPreview(null); + e.target.value = ""; + return; + } + + setSelectedLogo(file); + + // Create preview + const reader = new FileReader(); + reader.onloadend = () => { + setLogoPreview(reader.result as string); + }; + reader.readAsDataURL(file); + }; + const [stateChangeTeamProperties, changeTeamPropertiesFormAction] = useTypeSafeFormState( updateTeamSchema, async (data) => { - const updateTeamResponse = await updateTeamAction(data); + if (!isRegistrationStage) { + return toast.error("You can only update team information during the registration stage.", { + duration: 3000, + }); + } + + // Create FormData from form data and logo file + const formDataToSend = new FormData(); + formDataToSend.append("tournamentAbbreviation", data.tournamentAbbreviation); + formDataToSend.append("teamId", data.teamId); + formDataToSend.append("name", data.name); + if (selectedLogo) { + formDataToSend.append("logo", selectedLogo); + } + + const updateTeamResponse = await updateTeamAction(formDataToSend); if (!updateTeamResponse.status) { - return toast.error(updateTeamResponse.errorMessage, { + return toast.error(updateTeamResponse.errorMessage || "Failed to update team", { duration: 3000, }); } + toast.success("Team updated successfully", { + duration: 3000, + }); + setSelectedLogo(null); + setLogoPreview(null); + setLogoError(null); + router.refresh(); }, ); + if (!isRegistrationStage) { + return ( +
    +

    Team editing is only available during the registration stage.

    +
    + ); + } + return (
    - + + {/* Team Logo */} +
    + + + {logoError && ( +
    {logoError}
    + )} + {(logoPreview || (logoUrl && !selectedLogo && logoUrl !== "/aim_logo.svg")) && ( +
    + Preview: +
    +
    + +
    +
    +
    + )} +
    + diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DeleteParticipantButton.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DeleteParticipantButton.tsx new file mode 100644 index 0000000..06250ef --- /dev/null +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DeleteParticipantButton.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { deleteParticipantFromTeamAction } from "@/actions/public/deleteParticipantFromTeamAction"; + +interface DeleteParticipantButtonProps { + tournamentId: string; + teamId: string; + participantId: string; + isRegistrationStage: boolean; +} + +export const DeleteParticipantButton = ({ + tournamentId, + teamId, + participantId, + isRegistrationStage, +}: DeleteParticipantButtonProps) => { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + + const handleDelete = () => { + if (!isRegistrationStage) { + toast.error("You can only delete participants during the registration stage.", { + duration: 3000, + }); + return; + } + + startTransition(async () => { + const result = await deleteParticipantFromTeamAction( + tournamentId, + teamId, + participantId, + ); + if (result.status) { + toast.success("Participant removed successfully", { + duration: 3000, + }); + router.refresh(); + } else { + toast.error(result.errorMessage || "Failed to remove participant", { + duration: 3000, + }); + } + }); + }; + + return ( + + ); +}; + diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DisbandTeamButton.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DisbandTeamButton.tsx new file mode 100644 index 0000000..e46e6c6 --- /dev/null +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DisbandTeamButton.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { Button } from "@ui/atoms/Button/Button"; +import { disbandTeamAction } from "@/actions/public/disbandTeamAction"; + +interface DisbandTeamButtonProps { + tournamentId: string; + teamId: string; + isRegistrationStage: boolean; +} + +export const DisbandTeamButton = ({ + tournamentId, + teamId, + isRegistrationStage, +}: DisbandTeamButtonProps) => { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + + const handleDisband = () => { + if (!isRegistrationStage) { + toast.error("You can only disband team during the registration stage.", { + duration: 3000, + }); + return; + } + + startTransition(async () => { + const result = await disbandTeamAction(tournamentId, teamId); + if (result.status) { + toast.success("Team disbanded successfully", { + duration: 3000, + }); + router.push(`/tournament/${tournamentId}/teams`); + } else { + toast.error(result.errorMessage || "Failed to disband team", { + duration: 3000, + }); + } + }); + }; + + return ( + + ); +}; + diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx index 8b0c5df..4bfd377 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/InvitePlayerToTeamButton.tsx @@ -10,11 +10,13 @@ import { resetFormValues } from "@/lib/helpers"; export const InvitePlayerToTeamButton = ({ team: { teamId, tournamentAbbreviation }, + isRegistrationStage, }: { team: { tournamentAbbreviation: string; teamId: string; }; + isRegistrationStage: boolean; }) => { const formRef = React.useRef(null); const [stateInviteToTeam, inviteToTeamFormAction] = useTypeSafeFormState( @@ -34,6 +36,10 @@ export const InvitePlayerToTeamButton = ({ }, ); + if (!isRegistrationStage) { + return null; + } + return ( @@ -46,11 +51,16 @@ const TeamPage = async ({
    - team logo
    @@ -60,39 +70,11 @@ const TeamPage = async ({

    Status: {getTeam?.status}

    {isCaptain && ( - { - "use server"; - const cookie = cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - const { data: disbandTeamResponse } = await disbandTeam({ - path: { - abbreviation: tournamentId, - teamId, - }, - }); - - await multipleRevalidatePaths([ - `/tournament/${tournamentId}/teams`, - `/tournament/${tournamentId}`, - "/", - ]); - if (disbandTeamResponse) { - redirect(`/tournament/${tournamentId}/teams`); - } - }} - > - - + )} @@ -113,6 +95,7 @@ const TeamPage = async ({ logoUrl: getTeam?.logoUrl || "", tournamentAbbreviation: tournamentId, }} + isRegistrationStage={isRegistrationStage || false} />
    @@ -121,6 +104,7 @@ const TeamPage = async ({ teamId: getTeam?.id || "", tournamentAbbreviation: tournamentId, }} + isRegistrationStage={isRegistrationStage || false} />
    @@ -164,44 +148,12 @@ const TeamPage = async ({ {isCaptain && (
    )} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ba03808..1baf20b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -18,7 +18,7 @@ const monserrat = Montserrat({ // configure internal service client client.setConfig({ // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, + baseUrl: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080", // set default headers for requests }); diff --git a/src/formSchemas/editMatchSchema.ts b/src/formSchemas/editMatchSchema.ts new file mode 100644 index 0000000..d7b06a6 --- /dev/null +++ b/src/formSchemas/editMatchSchema.ts @@ -0,0 +1,14 @@ +import * as zod from "zod"; + +export const editMatchSchema = zod.object({ + matchId: zod.string(), + tournamentAbbreviation: zod.string(), + dataTimeStart: zod.string(), + stageType: zod.string(), + refereeIds: zod.array(zod.string()).optional(), + commentatorIds: zod.array(zod.string()).optional(), + streamerIds: zod.array(zod.string()).optional(), +}); + +export type EditMatchSchemaType = zod.infer; + diff --git a/src/formSchemas/rescheduleMatchSchema.ts b/src/formSchemas/rescheduleMatchSchema.ts new file mode 100644 index 0000000..b17933a --- /dev/null +++ b/src/formSchemas/rescheduleMatchSchema.ts @@ -0,0 +1,22 @@ +import * as zod from "zod"; + +export const rescheduleMatchSchema = zod.object({ + matchId: zod.string().uuid(), + proposedDateTime: zod.string().min(1, "Date and time is required"), + tournamentAbbreviation: zod.string(), + agreeToRules: zod + .preprocess( + (val) => { + // Convert checkbox value ("on" or undefined) to boolean + if (val === "on" || val === true) return true; + if (val === undefined || val === null || val === false) return false; + return Boolean(val); + }, + zod.boolean().refine((val) => val === true, { + message: "You must agree to the reschedule rules", + }), + ), +}); + +export type RescheduleMatchSchemaType = zod.infer; + diff --git a/src/formSchemas/updateTeamSchema.ts b/src/formSchemas/updateTeamSchema.ts index 16d1487..cd44d14 100644 --- a/src/formSchemas/updateTeamSchema.ts +++ b/src/formSchemas/updateTeamSchema.ts @@ -1,10 +1,9 @@ import * as zod from "zod"; export const updateTeamSchema = zod.object({ - logoUrl: zod.string(), - name: zod.string(), - teamId: zod.string(), - tournamentAbbreviation: zod.string(), + name: zod.string().min(1, "Team name is required"), + teamId: zod.string().uuid("Invalid team ID"), + tournamentAbbreviation: zod.string().min(1, "Tournament abbreviation is required"), }); export type updateTeamSchemaType = zod.infer; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index c77a7b8..97c2c36 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -58,7 +58,11 @@ export const resetFormValues = >({ schemaKeys.includes(input.name as keyof z.infer) && !resetWithoutInputNames.includes(input.name as keyof z.infer) ) { - input.value = ""; + if (input.type === "checkbox") { + (input as HTMLInputElement).checked = false; + } else { + input.value = ""; + } } }); }; diff --git a/src/lib/restClient.ts b/src/lib/restClient.ts index 04217a3..428c254 100644 --- a/src/lib/restClient.ts +++ b/src/lib/restClient.ts @@ -1,5 +1,5 @@ import { client } from "../../client"; client.setConfig({ - baseUrl: process.env.NEXT_PUBLIC_API_URL, + baseUrl: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080", }); diff --git a/src/ui/atoms/AvatarGroup/AvatarGroup.tsx b/src/ui/atoms/AvatarGroup/AvatarGroup.tsx new file mode 100644 index 0000000..2584ef9 --- /dev/null +++ b/src/ui/atoms/AvatarGroup/AvatarGroup.tsx @@ -0,0 +1,60 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; +import type { ParticipantResponseDto } from "../../../../client"; + +interface AvatarGroupProps { + participants: ParticipantResponseDto[]; + max?: number; +} + +export const AvatarGroup = ({ + participants, + max = 4, +}: AvatarGroupProps) => { + if (participants.length === 0) { + return null; + } + + const visibleParticipants = participants.slice(0, max); + const remainingCount = participants.length - max; + + return ( +
    + {visibleParticipants.map((participant, index) => ( +
    +
    + +
    +
    + ))} + {remainingCount > 0 && ( +
    +
    + +{remainingCount} +
    +
    + )} +
    + ); +}; + diff --git a/src/ui/atoms/Forms/Select/StaffMemberAutocomplete.tsx b/src/ui/atoms/Forms/Select/StaffMemberAutocomplete.tsx new file mode 100644 index 0000000..2e197e6 --- /dev/null +++ b/src/ui/atoms/Forms/Select/StaffMemberAutocomplete.tsx @@ -0,0 +1,207 @@ +"use client"; +import React, { useState, useEffect, useRef } from "react"; +import { getStaffMembersAction } from "@/actions/public/getStaffMembersAction"; + +export interface selectOptions { + id: string; + label: string; +} + +interface IStaffMemberAutocompleteProps { + name: string; + label: string; + tournamentAbbreviation: string; + selectedOption?: string[]; + errorMessage?: string; + required?: boolean; +} + +export const StaffMemberAutocomplete = ({ + name, + label, + tournamentAbbreviation, + selectedOption = [], + errorMessage, + required = false, +}: IStaffMemberAutocompleteProps) => { + const [isOpen, setIsOpen] = useState(false); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const dropdownRef = useRef(null); + const inputRef = useRef(null); + const [selectedLabelsMap, setSelectedLabelsMap] = useState>(new Map()); + const [selectedIds, setSelectedIds] = useState(selectedOption); + + // Sync with prop changes + useEffect(() => { + setSelectedIds(selectedOption); + }, [selectedOption]); + + // Load data when dropdown opens or if there are selected options on mount + useEffect(() => { + if (selectedOption.length > 0 && options.length === 0 && !loading) { + // Load data immediately if there are selected options + void loadStaffMembers(); + } else if (isOpen && options.length === 0 && !loading) { + void loadStaffMembers(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isOpen, selectedOption.length]); + + // Update selected labels map when options are loaded + useEffect(() => { + if (options.length > 0) { + const newMap = new Map(); + options.forEach((opt) => { + if (selectedIds.includes(opt.id)) { + newMap.set(opt.id, opt.label); + } + }); + setSelectedLabelsMap(newMap); + } + }, [options, selectedIds]); + + // Close dropdown when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const target = event.target as Node; + if ( + dropdownRef.current && + !dropdownRef.current.contains(target) && + inputRef.current && + !inputRef.current.contains(target) + ) { + setIsOpen(false); + setSearchTerm(""); + } + }; + + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + } + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [isOpen]); + + const loadStaffMembers = async () => { + if (loading) return; // Prevent multiple simultaneous loads + setLoading(true); + try { + const staffMemberOptions = await getStaffMembersAction(tournamentAbbreviation); + setOptions(staffMemberOptions); + } catch (error) { + console.error("Error loading staff members:", error); + } finally { + setLoading(false); + } + }; + + const toggleSelection = (optionId: string) => { + const newSelection = selectedIds.includes(optionId) + ? selectedIds.filter((id) => id !== optionId) + : [...selectedIds, optionId]; + setSelectedIds(newSelection); + }; + + const filteredOptions = options.filter((option) => + option.label.toLowerCase().includes(searchTerm.toLowerCase()), + ); + + // Get selected labels from map or from options + const selectedLabels = Array.from(selectedLabelsMap.values()).join(", ") || + options + .filter((opt) => selectedIds.includes(opt.id)) + .map((opt) => opt.label) + .join(", "); + + return ( +
    + +
    + { + setSearchTerm(e.target.value); + setIsOpen(true); + }} + onFocus={() => { + setIsOpen(true); + setSearchTerm(""); + }} + onBlur={(e) => { + // Don't close if clicking on dropdown + if (!dropdownRef.current?.contains(e.relatedTarget as Node)) { + setSearchTerm(""); + } + }} + /> + {isOpen && ( +
    +
    + {loading ? ( +
    + +

    Loading staff members...

    +
    + ) : filteredOptions.length === 0 ? ( +
    + No staff members found +
    + ) : ( + filteredOptions.map((option) => { + const isSelected = selectedIds.includes(option.id); + return ( + + ); + }) + )} +
    +
    + )} +
    + {/* Hidden inputs for form submission */} + {selectedIds.map((id) => ( + + ))} +
    + {errorMessage} +
    +
    + ); +}; + diff --git a/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx b/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx new file mode 100644 index 0000000..708d2ba --- /dev/null +++ b/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx @@ -0,0 +1,217 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { type BeatmapModificationResponseDto } from "../../../../client"; + +interface BeatmapListItemProps { + href: string; + title: string; + artist: string; + version: string; + creator: string; + modification?: BeatmapModificationResponseDto["modification"]; + position: number; + isCustom: boolean; + img?: string; + mapInformation: { + stars: number; + time: number; + bpm: number; + ar: number; + hp: number; + od: number; + cs: number; + }; + _tournamentAbbreviation: string; + _beatmapId: number; + _beatmapsetId: number; +} + +export const BeatmapListItem = ({ + href, + title, + artist, + version, + creator, + modification, + position, + isCustom, + img, + mapInformation, + _tournamentAbbreviation, + _beatmapId, + _beatmapsetId, +}: BeatmapListItemProps) => { + const formatTime = (seconds: number) => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + const formattedSeconds = remainingSeconds < 10 ? "0" + remainingSeconds : remainingSeconds; + return `${minutes}:${formattedSeconds}`; + }; + + const getModificationColor = (mod?: string) => { + switch (mod) { + case "NM": + return "bg-blue-600"; + case "HD": + return "bg-yellow-500"; + case "HR": + return "bg-orange-500"; + case "DT": + return "bg-purple-500"; + case "FM": + return "bg-pink-500"; + case "TB": + return "bg-red-500"; + default: + return "bg-gray-600"; + } + }; + + const getModificationLabel = (mod?: string, pos?: number) => { + if (!mod) return ""; + if (mod === "TB") return "TB"; + return `${mod}${pos || ""}`; + }; + + const getModificationGradient = (mod?: string) => { + switch (mod) { + case "NM": + return "transparent"; + case "DT": + return "linear-gradient(to right, rgba(75, 0, 130, 0.3), rgba(75, 0, 130, 0.1), transparent)"; + case "HD": + return "linear-gradient(to right, rgba(255, 215, 0, 0.3), rgba(255, 215, 0, 0.1), transparent)"; + case "HR": + return "linear-gradient(to right, rgba(220, 20, 60, 0.3), rgba(220, 20, 60, 0.1), transparent)"; + case "TB": + return "linear-gradient(to right, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.2), transparent)"; + case "FM": + return "linear-gradient(to right, rgba(0, 255, 255, 0.3), rgba(0, 255, 255, 0.1), transparent)"; + case "FL": + return "linear-gradient(to right, rgba(128, 128, 128, 0.3), rgba(128, 128, 128, 0.1), transparent)"; + case "EZ": + return "linear-gradient(to right, rgba(0, 128, 0, 0.3), rgba(0, 128, 0, 0.1), transparent)"; + case "HT": + return "linear-gradient(to right, rgba(255, 140, 0, 0.3), rgba(255, 140, 0, 0.1), transparent)"; + default: + return "transparent"; + } + }; + + return ( +
    + {/* Gradient overlay based on modification - starts from right edge of image */} +
    + + {/* Left section with background image */} + e.stopPropagation()} + className="relative flex-shrink-0 w-64 overflow-hidden rounded-l-lg" + > + + + + {/* Center section with song info */} +
    +

    + {title} +

    +

    + [{version}] +

    +

    + {artist} +

    +

    + Mapped by {creator} +

    +
    + + {/* Custom beatmap logo background - between title and modification badge */} + {isCustom && ( +
    + )} + + {/* Right section with map details */} +
    + {/* Modification badge */} + {modification && ( +
    + {getModificationLabel(modification, modification !== "TB" ? position : undefined)} +
    + )} + + {/* Difficulty info */} +
    + {/* Star rating */} +
    + {mapInformation.stars.toFixed(1)}★ +
    + {/* Matryca 3x2 */} +
    + {/* Kolumna 1: length nad BPM */} + + {formatTime(mapInformation.time)} + + {/* Kolumna 2: AR nad HP */} + + AR {mapInformation.ar.toFixed(1)} + + {/* Kolumna 3: CS nad OD */} + + CS {mapInformation.cs.toFixed(1)} + + {/* Kolumna 1, wiersz 2: BPM pod length */} + + {mapInformation.bpm.toFixed(0)} bpm + + {/* Kolumna 2, wiersz 2: HP pod AR */} + + HP {mapInformation.hp.toFixed(1)} + + {/* Kolumna 3, wiersz 2: OD pod CS */} + + OD {mapInformation.od.toFixed(1)} + +
    +
    +
    +
    + ); +}; + diff --git a/src/ui/molecules/Cards/TeamCard.tsx b/src/ui/molecules/Cards/TeamCard.tsx index 2c49e3d..7c710c8 100644 --- a/src/ui/molecules/Cards/TeamCard.tsx +++ b/src/ui/molecules/Cards/TeamCard.tsx @@ -1,6 +1,7 @@ import React from "react"; import { PiCrownSimpleFill } from "react-icons/pi"; import Link from "next/link"; +import Image from "next/image"; import { type TeamResponseDto } from "../../../../client"; import { Avatar } from "@ui/atoms/Avatar/Avatar"; @@ -20,7 +21,13 @@ export const TeamCard = ({ className={"flex w-full flex-col gap-4 rounded-md bg-tuned p-6 text-primary-light"} >
    - +

    {team?.name}

    diff --git a/src/ui/organisms/Footer/Footer.tsx b/src/ui/organisms/Footer/Footer.tsx index 31df90e..62305c5 100644 --- a/src/ui/organisms/Footer/Footer.tsx +++ b/src/ui/organisms/Footer/Footer.tsx @@ -4,9 +4,13 @@ import { SiKofi } from "react-icons/si"; import { FaDiscord, FaTwitch } from "react-icons/fa"; import { FaXTwitter } from "react-icons/fa6"; import Link from "next/link"; -import { getTournaments } from "../../../../client"; +import { client, getTournaments } from "../../../../client"; export const Footer = async () => { + const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"; + client.setConfig({ + baseUrl: baseUrl, + }); const { data } = await getTournaments(); return ( diff --git a/src/ui/organisms/StageNavigation/StageNavigation.tsx b/src/ui/organisms/StageNavigation/StageNavigation.tsx new file mode 100644 index 0000000..d438ae3 --- /dev/null +++ b/src/ui/organisms/StageNavigation/StageNavigation.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import Link from "next/link"; +import { type StageResponseDto } from "../../../../client"; +import { stageTypeEnumToString } from "@/lib/helpers"; + +interface StageNavigationProps { + stages: StageResponseDto[]; + currentStage: StageResponseDto["stageType"]; + tournamentAbbreviation: string; +} + +export const StageNavigation = ({ + stages, + currentStage, + tournamentAbbreviation, +}: StageNavigationProps) => { + const availableStages = stages + .filter((stage) => !!stage.mappool) + .filter((stage) => stage.stageType !== "REGISTRATION") + .filter((stage) => stage.stageType !== "SCREENING") + .sort((a, b) => { + // Sort stages in tournament order + const order = [ + "QUALIFICATION", + "RO128", + "RO64", + "RO32", + "RO16", + "QUARTER_FINAL", + "SEMI_FINAL", + "FINAL", + "GRAND_FINAL", + ]; + const aIndex = order.indexOf(a.stageType); + const bIndex = order.indexOf(b.stageType); + return aIndex - bIndex; + }); + + return ( +
    +
    +
    + {availableStages.map((stage) => { + const isActive = stage.stageType === currentStage; + return ( + + {stageTypeEnumToString(stage.stageType)} + + ); + })} +
    +
    +
    + ); +}; + From 21fa7aac3842ec8fc90ba093c3ba2e9cdbcf4978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 21 Dec 2025 22:18:03 +0100 Subject: [PATCH 37/37] fix: Fixed errors --- src/actions/admin/adminTeamActions.ts | 27 ++++++++++++++----- .../settings/page.tsx | 12 ++++----- .../mappool/[stageType]/page.tsx | 6 ++--- .../teams/[teamId]/ChangeTeamNameForm.tsx | 3 ++- .../BeatmapListItem/BeatmapListItem.tsx | 3 --- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/actions/admin/adminTeamActions.ts b/src/actions/admin/adminTeamActions.ts index 559dcf4..e682b0f 100644 --- a/src/actions/admin/adminTeamActions.ts +++ b/src/actions/admin/adminTeamActions.ts @@ -1,9 +1,14 @@ "use server"; import { cookies } from "next/headers"; -import { client, addParticipantToTeam, removeParticipantFromTeam, changeTeamCaptain, deleteTeam, changeTeamStatus } from "../../../client"; +import { client, addParticipantToTeam, removeParticipantFromTeam, changeTeamCaptain, deleteTeam, changeTeamStatus, type ErrorResponse } from "../../../client"; import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +// Type guard to check if error is ErrorResponse +function isErrorResponse(error: unknown): error is ErrorResponse { + return typeof error === "object" && error !== null && "errors" in error; +} + export async function updateTeamAction(formDataToSend: FormData) { "use server"; const cookie = cookies().get("JWT")?.value; @@ -96,7 +101,9 @@ export async function addParticipantAction( }); if (error) { - const errorMessage: string = error.errors?.join(", ") || "Failed to add participant"; + const errorMessage: string = isErrorResponse(error) + ? (error.errors?.join(", ") || "Failed to add participant") + : "Failed to add participant"; return { status: false as const, errorMessage, @@ -138,7 +145,9 @@ export async function removeParticipantAction( }); if (error) { - const errorMessage: string = error.errors?.join(", ") || "Failed to remove participant"; + const errorMessage: string = isErrorResponse(error) + ? (error.errors?.join(", ") || "Failed to remove participant") + : "Failed to remove participant"; return { status: false as const, errorMessage, @@ -180,7 +189,9 @@ export async function changeCaptainAction( }); if (error) { - const errorMessage: string = error.errors?.join(", ") || "Failed to change captain"; + const errorMessage: string = isErrorResponse(error) + ? (error.errors?.join(", ") || "Failed to change captain") + : "Failed to change captain"; return { status: false as const, errorMessage, @@ -220,7 +231,9 @@ export async function deleteTeamAction( }); if (error) { - const errorMessage: string = error.errors?.join(", ") || "Failed to delete team"; + const errorMessage: string = isErrorResponse(error) + ? (error.errors?.join(", ") || "Failed to delete team") + : "Failed to delete team"; return { status: false as const, errorMessage, @@ -263,7 +276,9 @@ export async function changeTeamStatusAction( }); if (error) { - const errorMessage: string = error.errors?.join(", ") || "Failed to change team status"; + const errorMessage: string = isErrorResponse(error) + ? (error.errors?.join(", ") || "Failed to change team status") + : "Failed to change team status"; return { status: false as const, errorMessage, diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx index a9d24e8..f5d145d 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx @@ -149,9 +149,9 @@ const SettingsPage = () => { setTournamentData((prev) => { const prizePool = [...(prev?.prizePool || [])]; if (prizePool[0]) { - prizePool[0] = { ...prizePool[0], prize: prize || 0 }; + prizePool[0] = { ...prizePool[0], prize: String(prize || 0) }; } else { - prizePool[0] = { type: 0, prize: prize || 0 }; + prizePool[0] = { type: 0, prize: String(prize || 0) }; } return { ...prev, @@ -178,9 +178,9 @@ const SettingsPage = () => { setTournamentData((prev) => { const prizePool = [...(prev?.prizePool || [])]; if (prizePool[1]) { - prizePool[1] = { ...prizePool[1], prize: prize || 0 }; + prizePool[1] = { ...prizePool[1], prize: String(prize || 0) }; } else { - prizePool[1] = { type: 1, prize: prize || 0 }; + prizePool[1] = { type: 1, prize: String(prize || 0) }; } return { ...prev, @@ -207,9 +207,9 @@ const SettingsPage = () => { setTournamentData((prev) => { const prizePool = [...(prev?.prizePool || [])]; if (prizePool[2]) { - prizePool[2] = { ...prizePool[2], prize: prize || 0 }; + prizePool[2] = { ...prizePool[2], prize: String(prize || 0) }; } else { - prizePool[2] = { type: 2, prize: prize || 0 }; + prizePool[2] = { type: 2, prize: String(prize || 0) }; } return { ...prev, diff --git a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx index 05cceab..7c03bf0 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx @@ -125,9 +125,9 @@ const SingleTournamentMappool = async ({ od: map.beatmapStatistics.od, cs: map.beatmapStatistics.cs, }} - tournamentAbbreviation={params.tournamentId} - beatmapId={map.beatmapId} - beatmapsetId={map.beatmapsetId} + _tournamentAbbreviation={params.tournamentId} + _beatmapId={map.beatmapId} + _beatmapsetId={map.beatmapsetId} /> ))}

    diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx index 6fc7526..987556a 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/ChangeTeamNameForm.tsx @@ -113,7 +113,8 @@ export const ChangeTeamNameForm = ({ const updateTeamResponse = await updateTeamAction(formDataToSend); if (!updateTeamResponse.status) { - return toast.error(updateTeamResponse.errorMessage || "Failed to update team", { + const errorMsg: string = updateTeamResponse.errorMessage || "Failed to update team"; + return toast.error(errorMsg, { duration: 3000, }); } diff --git a/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx b/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx index 708d2ba..893665e 100644 --- a/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx +++ b/src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx @@ -40,9 +40,6 @@ export const BeatmapListItem = ({ isCustom, img, mapInformation, - _tournamentAbbreviation, - _beatmapId, - _beatmapsetId, }: BeatmapListItemProps) => { const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60);
    Number Start date time Roster Referee
    {room.number} {format(new Date(room.startDate), "dd/MM/yyyy HH:mm")} {room.tournamentType === tournamentType.PARTICIPANT_VS From da6df15ab27376773a7f2be7032a8673f3e6def3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 15 Sep 2024 18:27:10 +0200 Subject: [PATCH 18/37] fix: added number of QR --- .../[tournamentAbbreviation]/qualification-rooms/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx index ce66e22..b1c96d6 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx @@ -137,6 +137,7 @@ const QRoomsPage = async ({ + @@ -144,8 +145,9 @@ const QRoomsPage = async ({ - {qualificationRooms(getQualificationRoomsData || []).map((room) => ( + {qualificationRooms(getQualificationRoomsData || []).map((room, index) => ( + - {qualificationRooms(getQualificationRoomsData || []).map((room, index) => ( + {qualificationRooms(getQualificationRoomsData || []).map((room) => ( - + - + diff --git a/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx index 0e4a373..fe9ac4a 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/qualification-rooms/page.tsx @@ -85,7 +85,7 @@ const QualificationRoomsPage = async ({
    Number Start date time Roster Referee
    {index + 1} {format(new Date(room.startDate), "dd/MM/yyyy HH:mm")} {room.tournamentType === tournamentType.PARTICIPANT_VS From d411a218c204fb5eb5426e7ff3408175f38a2712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 15 Sep 2024 18:44:00 +0200 Subject: [PATCH 19/37] fix: fixed number of QR --- .../[tournamentAbbreviation]/qualification-rooms/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx index b1c96d6..4ce820d 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/qualification-rooms/page.tsx @@ -145,9 +145,9 @@ const QRoomsPage = async ({
    {index + 1}{room.number} {format(new Date(room.startDate), "dd/MM/yyyy HH:mm")} {room.tournamentType === tournamentType.PARTICIPANT_VS From 519605cdb6229337ff2e1f9fb43387a09a948ed5 Mon Sep 17 00:00:00 2001 From: Patryk Date: Sun, 15 Sep 2024 20:18:00 +0200 Subject: [PATCH 20/37] fix: fixed mappool --- .../mappool/[stageType]/page.tsx | 184 +++----------- src/ui/molecules/Cards/MappoolCard.tsx | 226 +++++++----------- 2 files changed, 126 insertions(+), 284 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx index d4f1f64..9865628 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx @@ -1,11 +1,9 @@ import React from "react"; -import Link from "next/link"; import { cookies } from "next/headers"; import { type BeatmapModificationResponseDto, client, getMappoolByStage, - type modification, type StageResponseDto, } from "../../../../../../../client"; import { stageTypeEnumToString } from "@/lib/helpers"; @@ -35,7 +33,7 @@ const SingleTournamentMappool = async ({ Cookie: `token=${cookie}`, }, }); - let getMappoolByStageData, mappolStages; + let getMappoolByStageData; try { const { data: getMappoolByStageData1 } = await getMappoolByStage({ path: { @@ -44,156 +42,19 @@ const SingleTournamentMappool = async ({ }, }); getMappoolByStageData = getMappoolByStageData1; - mappolStages = getMappoolByStageData?.beatmapsModifications.filter( - (stage) => !!stage.beatmaps, - ); } catch (error) { console.error(error); } - const selectedMappoolByModification = searchParams.modification - ? getMappoolByStageData?.beatmapsModifications.filter( - (m) => m.modification === searchParams.modification, - ) - : getMappoolByStageData?.beatmapsModifications; - - const modifications = mappolStages - ?.filter((m) => !m.isHidden) - ?.filter((m) => m.beatmaps?.length && m.beatmaps?.length > 0) - .map((m) => m.modification); - - const countModificationBeatmaps: - | { - [key: string]: number; - } - | undefined = getMappoolByStageData?.beatmapsModifications.reduce( - (acc, mod) => { - acc[mod.modification] = mod.beatmaps?.length || 0; - return acc; - }, - {} as { [key: string]: number }, - ); - - const allModificationsCount = - getMappoolByStageData?.beatmapsModifications?.flatMap((m) => m.beatmaps).length || 0; - - const mappoollCard: React.ReactNode[] = []; - - if (searchParams.modification) { - selectedMappoolByModification && - selectedMappoolByModification[0]?.beatmaps?.forEach((map) => { - mappoollCard.push( - , - ); - }); - } else { - selectedMappoolByModification - ?.flatMap((m) => m.beatmaps) - ?.forEach((map) => { - mappoollCard.push( - , - ); - }); - } - - const stageContent = ( - <> -
    -
    -

    - {stageTypeEnumToString(params.stageType)} -

    - {allModificationsCount > 0 ? ( -
      -
    • - - ALL - {allModificationsCount} - -
    • - {modifications?.map((mod, index) => { - const isActive = searchParams.modification === mod; - if (!countModificationBeatmaps) { - return null; - } - const isMappoolEmpty = countModificationBeatmaps[mod] === 0; - - if (isMappoolEmpty) { - return null; - } - - return ( -
    • - - {mod} - - {countModificationBeatmaps[mod]} - - -
    • - ); - })} -
    - ) : ( - "No mappool beatmaps found." - )} -
    -
    -
    {mappoollCard}
    - - ); - return ( <>
    -
    +

    Mappool

    +

    + {stageTypeEnumToString(params.stageType)} +

    @@ -201,7 +62,40 @@ const SingleTournamentMappool = async ({ )} - {stageContent} + {getMappoolByStageData?.beatmapsModifications.map((bm) => ( +
    + {bm?.beatmaps + ?.filter((map) => { + if (!searchParams.modification) { + return true; + } + return map.modification === searchParams.modification; + }) + ?.map((map) => ( + + ))} +
    + ))}
    ); diff --git a/src/ui/molecules/Cards/MappoolCard.tsx b/src/ui/molecules/Cards/MappoolCard.tsx index 5c13147..14122ce 100644 --- a/src/ui/molecules/Cards/MappoolCard.tsx +++ b/src/ui/molecules/Cards/MappoolCard.tsx @@ -8,14 +8,18 @@ import { TextBox } from "@ui/atoms/TextBox/TextBox"; export const MappoolCard = ({ title, + version, modification, isCustom, img, author, mapInformation, position, + href, }: { + href: string; title?: string; + version?: string; modification?: BeatmapModificationResponseDto["modification"]; isCustom: boolean; img?: string; @@ -32,152 +36,96 @@ export const MappoolCard = ({ }; }) => { return ( -
    -
    - mappool-card - - mappool-card - {isCustom && ( -
    - Custom -
    - )} - -
    NumberStart date timeStart date time (UTC+0) Roster Referee Actions
    - + {isUserCaptain && } From e6743b3702d6c72ebbeba2618f11e5955742aaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sun, 22 Sep 2024 14:56:01 +0200 Subject: [PATCH 28/37] feat: matches list --- .../[tournamentId]/schedule/page.tsx | 79 +++++++++++++++---- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx index db9d37c..85011d2 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx @@ -1,7 +1,8 @@ import React from "react"; import { cookies } from "next/headers"; -import { client, getStages } from "../../../../../../client"; -import { ScheduleList } from "@ui/organisms/ScheduleList/ScheduleList"; +import { format } from "date-fns"; +import { client, getMatches } from "../../../../../../client"; +import Section from "@ui/atoms/Section/Section"; const SingleTournamentSchedule = async ({ params, @@ -20,27 +21,71 @@ const SingleTournamentSchedule = async ({ Cookie: `token=${cookie}`, }, }); - const { data } = await getStages({ + const { data } = await getMatches({ path: { abbreviation: params.tournamentId, }, }); return ( -
    -
    -
    -
    -

    Schedule

    -
    +
    +
    +

    Match schedule

    + +
    +
    Start date timeStart date time (UTC+0) Roster RefereeActions
    + + + + + + + + + + + + + {data + ?.filter((match) => match.matchResult === null) + ?.sort((a, b) => { + return a.startDate > b.startDate ? 1 : -1; + }) + .map((match) => ( + + + + + + + + + + ))} + +
    Start date time (UTC+0)StageTeam blueTeam redRefereeCommentatorsStreamers
    + {format(new Date(match.startDate), "dd/MM/yyyy HH:mm")} + {match.stage?.stageType}{match.teamBlue.name}{match.teamRed.name} + {match.referees?.map((referee) => ( +
    + {referee?.user?.username} +
    + ))} +
    + {match.commentators?.map((commentator) => ( +
    + {commentator.user?.username} +
    + ))} +
    + {match.streamers?.map((streamer) => ( +
    + {streamer?.user?.username} +
    + ))} +
    - - - + + ); }; From d7c148e3dde0c76faf40512129b2689982a11763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Or=C5=82owski?= Date: Tue, 24 Sep 2024 21:06:55 +0200 Subject: [PATCH 29/37] feat: added matches --- src/actions/admin/adminAddMatchActions.ts | 52 ++ .../matches/MatchesModal.tsx | 302 ++++++++++ .../[tournamentAbbreviation]/matches/page.tsx | 534 +++++++++++++++++- src/formSchemas/addMachSchema.ts | 14 + 4 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 src/actions/admin/adminAddMatchActions.ts create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx create mode 100644 src/formSchemas/addMachSchema.ts diff --git a/src/actions/admin/adminAddMatchActions.ts b/src/actions/admin/adminAddMatchActions.ts new file mode 100644 index 0000000..7b128fa --- /dev/null +++ b/src/actions/admin/adminAddMatchActions.ts @@ -0,0 +1,52 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, createMatch, type stageType } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import { type AddMachSchemaType } from "@/formSchemas/addMachSchema"; + +export async function addMatchAction(formData: AddMachSchemaType) { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data, error } = await createMatch({ + path: { + abbreviation: formData.tournamentAbbreviation, + }, + body: { + startDate: formData.dataTimeStart, + commentatorIds: formData.commentatorIds, + refereeIds: formData.refereeIds, + streamerIds: formData.streamerIds, + stageType: formData.stageType as stageType, + teamBlueId: formData.teamBlueId, + teamRedId: formData.teamRedId, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/matches`, + `/tournament/${formData.tournamentAbbreviation}/schedule`, + ]); + + return { + status: true as const, + response: data, + }; +} diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx new file mode 100644 index 0000000..19a39ac --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx @@ -0,0 +1,302 @@ +"use client"; +import React, { useRef } from "react"; +import { toast } from "sonner"; +import { stageType } from "../../../../../../../client"; +import { Button } from "@ui/atoms/Button/Button"; +import Modal from "@ui/organisms/Modal/Modal"; +import { ComboBox, type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; +import { Input } from "@ui/atoms/Forms/Input/Input"; +import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; +import { editQualificationRoomsAction } from "@/actions/admin/adminQualificationRoomsActions"; +import { resetFormValues, stageTypeEnumToString } from "@/lib/helpers"; +import { editQualificationRoomsSchema } from "@/formSchemas/editQualificationRoomSchema"; +import { addMatchSchema } from "@/formSchemas/addMachSchema"; +import { addMatchAction } from "@/actions/admin/adminAddMatchActions"; + +type AddNewQRoom = { + type: "add"; +}; + +type EditQRoom = { + type: "edit"; + room: { + roomId: string; + dataTimeStart: string; + selectedStaffMemberOption: selectOptions[]; + selectedRosterIds: selectOptions[]; + }; + staffMemberSelectOptions: selectOptions[]; + rostersSelectOptions: selectOptions[]; +}; + +type QRoomModalType = AddNewQRoom | EditQRoom; + +interface IAddStaffMemberProps { + tournamentAbb: string; + modalType: QRoomModalType; + teams: selectOptions[]; + staffMembers: selectOptions[]; +} + +export const MatchesModal = ({ + tournamentAbb, + modalType, + teams, + staffMembers, +}: IAddStaffMemberProps) => { + const modalRef = useRef(null); + const formRef = React.useRef(null); + + const stageTypeSelectOptions: selectOptions[] = [ + { + id: stageType.FINAL, + label: stageTypeEnumToString(stageType.FINAL), + }, + { + id: stageType.RO16, + label: stageTypeEnumToString(stageType.RO16), + }, + { + id: stageType.RO64, + label: stageTypeEnumToString(stageType.RO64), + }, + { + id: stageType.GRAND_FINAL, + label: stageTypeEnumToString(stageType.GRAND_FINAL), + }, + { + id: stageType.QUARTER_FINAL, + label: stageTypeEnumToString(stageType.QUARTER_FINAL), + }, + { + id: stageType.RO32, + label: stageTypeEnumToString(stageType.RO32), + }, + { + id: stageType.RO128, + label: stageTypeEnumToString(stageType.RO128), + }, + { + id: stageType.SEMI_FINAL, + label: stageTypeEnumToString(stageType.SEMI_FINAL), + }, + ]; + + const [stateCreateMatch, formCreateMatch] = useTypeSafeFormState( + addMatchSchema, + async (data) => { + const addMatchActionResponse = await addMatchAction(data); + if (!addMatchActionResponse.status) { + return toast.error(addMatchActionResponse.errorMessage, { + duration: 3000, + }); + } + modalRef.current?.close(); + // resetFormValues({ + // formRef, + // resetWithoutInputNames: ["tournamentAbbreviation"], + // schema: createQualificationRoomsSchema, + // }); // todo, czyscieczenie + }, + ); + + const [stateEditQRoom, formActionEditQRoom] = useTypeSafeFormState( + editQualificationRoomsSchema, + async (data) => { + const editMemberResponse = await editQualificationRoomsAction(data); + if (!editMemberResponse.status) { + return toast.error(editMemberResponse.errorMessage, { + duration: 3000, + }); + } + modalRef.current?.close(); + resetFormValues({ + formRef, + resetWithoutInputNames: ["tournamentAbbreviation", "roomId", "dataTimeStart"], + schema: editQualificationRoomsSchema, + }); + }, + ); // todo nie dziala to sa w ogole glupoty tutaj + + return ( + <> + {modalType.type === "add" ? ( + + ) : ( + + )} + + +

    {modalType.type === "add" ? "Create match" : `Edit qualification rooms`}

    +
    +
    + + + stage.id === modalType.stage.stageType, + // )?.id || "", + // ] + // : [] + // } + readonly={modalType.type === "edit" ? true : false} + /> + stage.id === modalType.stage.stageType, + // )?.id || "", + // ] + // : [] + // } + // readonly={modalType.type === "edit" ? true : false} + /> + stage.id === modalType.stage.stageType, + // )?.id || "", + // ] + // : [] + // } + // readonly={modalType.type === "edit" ? true : false} + /> + staff.id) + : undefined + } + // type={modalType.type === "edit" ? undefined : "hidden"} + // disabled={modalType.type === "add"} + // hidden={modalType.type === "add"} + multiple={true} + // errorMessage={ + // stateEditQRoom?.errors.rosterIds && + // stateEditQRoom?.errors.rosterIds[0] + // } + /> + + staff.id) + : undefined + } + // type={modalType.type === "edit" ? undefined : "hidden"} + // disabled={modalType.type === "add"} + // hidden={modalType.type === "add"} + multiple={true} + // errorMessage={ + // stateEditQRoom?.errors.rosterIds && + // stateEditQRoom?.errors.rosterIds[0] + // } + /> + + staff.id) + : undefined + } + // type={modalType.type === "edit" ? undefined : "hidden"} + // disabled={modalType.type === "add"} + // hidden={modalType.type === "add"} + multiple={true} + // errorMessage={ + // stateEditQRoom?.errors.rosterIds && + // stateEditQRoom?.errors.rosterIds[0] + // } + /> +
    + +
    +
    + + ); +}; diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx index a60be8f..54885f2 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx @@ -1,7 +1,537 @@ import React from "react"; +import Image from "next/image"; +import { format } from "date-fns"; +import { cookies } from "next/headers"; +import { + client, + getMatches, + getParticipants, + getStaffMembers1, + getTeamsByTournament, + getTournamentByAbbreviation, + getTournamentStaffMember, + signInMatchStaffMember, + tournamentType, +} from "../../../../../../../client"; +import { type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; +import { getUser } from "@/actions/public/getUserAction"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import { MatchesModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal"; -const MatchesPage = () => { - return
    nie ma pospiechu L)
    ; +const MatchesPage = async ({ + params: { tournamentAbbreviation }, +}: { + params: { + tournamentAbbreviation: string; + }; +}) => { + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const userData = await getUser(); + const { data: getStaffForATournamentUser } = await getTournamentStaffMember({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + + const { data: getStaffMembers } = await getStaffMembers1({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + + const { data: getTournament } = await getTournamentByAbbreviation({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + + const staffMemberSelectOptions: selectOptions[] = + getStaffMembers + ?.filter((s) => s.user) + ?.map((staffMember) => ({ + id: staffMember.id, + label: staffMember.user ? staffMember.user.username : staffMember.username || "", + })) || []; + + let rostersSelectOptions: selectOptions[] = []; + if (getTournament?.tournamentType !== tournamentType.PARTICIPANT_VS) { + const { data: getTeams } = await getTeamsByTournament({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + rostersSelectOptions = + getTeams?.map((team) => ({ + id: team.id, + label: team.name, + })) || []; + } else { + const { data: getParticipantsData } = await getParticipants({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + rostersSelectOptions = + getParticipantsData?.map((participant) => ({ + id: participant.id, + label: participant.user.username, + })) || []; + } + + const { data } = await getMatches({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + + const canSignIn = getStaffForATournamentUser?.permissions?.some( + (permission) => permission === "MATCH_STAFF_MEMBER_SIGN_IN", + ); + + return ( +
    +

    Matches

    + + +
    +
    + + + + + + + + + + + + + + + {data + ?.filter((match) => match.matchResult === null) + ?.sort((a, b) => { + return a.startDate > b.startDate ? 1 : -1; + }) + .map((match) => ( + + + + + + + + + + + ))} + +
    Start date time (UTC+0)StageTeam blueTeam redRefereeCommentatorsStreamersAction
    + {format(new Date(match.startDate), "dd/MM/yyyy HH:mm")} + {match.stage?.stageType}{match.teamBlue.name}{match.teamRed.name} + {match.referees ? ( + match.referees.some( + (staffMember) => + staffMember.user?.id === userData?.id, + ) ? ( +
    { + "use server"; + const cookie = + cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: false, + type: "REFEREE", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + ) : ( +
    + {match.referees?.map((referee) => ( + <> +
    +
    + referee +
    +
    + {referee?.user?.username} + + ))} +
    + ) + ) : canSignIn ? ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: true, + type: "REFEREE", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + ) : ( + "-" + )} +
    + {match.commentators ? ( + match.commentators.some( + (staffMember) => + staffMember.user?.id === userData?.id, + ) ? ( +
    { + "use server"; + const cookie = + cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: false, + type: "COMMENTATOR", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + ) : ( +
    + {match.commentators?.map((com) => ( + <> +
    +
    + commentator +
    +
    + {com?.user?.username} + + ))} +
    + ) + ) : canSignIn ? ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: true, + type: "COMMENTATOR", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + ) : ( + "-" + )} +
    + {match.streamers ? ( + match.streamers.some( + (staffMember) => + staffMember.user?.id === userData?.id, + ) ? ( +
    { + "use server"; + const cookie = + cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: false, + type: "STREAMER", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + ) : ( +
    + {match.streamers?.map((st) => ( + <> +
    +
    + streamer +
    +
    + {st?.user?.username} + + ))} +
    + ) + ) : canSignIn ? ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: true, + type: "STREAMER", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + ) : ( + "-" + )} +
    (edit)
    +
    +
    + +
    +

    Finished matches

    + +
    + + + + + + + + + + + + + + {data + ?.filter((match) => match.matchResult !== null) + ?.sort((a, b) => { + return a.startDate > b.startDate ? 1 : -1; + }) + .map((match) => ( + + + + + + + + + + ))} + +
    Start date time (UTC+0)StageTeam blueTeam redRefereeCommentatorsStreamers
    + {format(new Date(match.startDate), "dd/MM/yyyy HH:mm")} + {match.stage?.stageType}{match.teamBlue.name}{match.teamRed.name} + {match.referees?.map((referee) => ( +
    + {referee?.user?.username} +
    + ))} +
    + {match.commentators?.map((commentator) => ( +
    + {commentator.user?.username} +
    + ))} +
    + {match.streamers?.map((streamer) => ( +
    + {streamer?.user?.username} +
    + ))} +
    +
    +
    +
    + ); }; export default MatchesPage; diff --git a/src/formSchemas/addMachSchema.ts b/src/formSchemas/addMachSchema.ts new file mode 100644 index 0000000..6d422e0 --- /dev/null +++ b/src/formSchemas/addMachSchema.ts @@ -0,0 +1,14 @@ +import * as zod from "zod"; + +export const addMatchSchema = zod.object({ + dataTimeStart: zod.string(), + tournamentAbbreviation: zod.string(), + stageType: zod.string(), + teamBlueId: zod.string(), + teamRedId: zod.string(), + refereeIds: zod.array(zod.string()).optional(), + commentatorIds: zod.array(zod.string()).optional(), + streamerIds: zod.array(zod.string()).optional(), +}); + +export type AddMachSchemaType = zod.infer; From 7a026e42b3567c15049cb02b29c419d25afc9e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 24 Sep 2024 21:58:21 +0200 Subject: [PATCH 30/37] feat: adding matches + sign in/out --- .../matches/MatchesModal.tsx | 6 +- .../[tournamentAbbreviation]/matches/page.tsx | 397 +++++++++--------- 2 files changed, 199 insertions(+), 204 deletions(-) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx index 19a39ac..f751515 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx @@ -238,7 +238,7 @@ export const MatchesModal = ({ staff.id) @@ -257,7 +257,7 @@ export const MatchesModal = ({ staff.id) @@ -276,7 +276,7 @@ export const MatchesModal = ({ staff.id) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx index 54885f2..54c7423 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx @@ -11,6 +11,7 @@ import { getTournamentByAbbreviation, getTournamentStaffMember, signInMatchStaffMember, + type StaffMemberResponseDto, tournamentType, } from "../../../../../../../client"; import { type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; @@ -93,9 +94,27 @@ const MatchesPage = async ({ }, }); - const canSignIn = getStaffForATournamentUser?.permissions?.some( - (permission) => permission === "MATCH_STAFF_MEMBER_SIGN_IN", - ); + const canSignIn = getStaffForATournamentUser?.permissions?.some((permission) => { + const b = permission === "MATCH_STAFF_MEMBER_SIGN_IN"; + return b; + }); + + const showSignOut = (staffMembers: StaffMemberResponseDto[] | undefined) => { + return ( + staffMembers?.some((staffMember) => { + return staffMember.user?.id === userData?.id; + }) && canSignIn + ); + }; + + const showSignIn = (staffMembers: StaffMemberResponseDto[] | undefined) => { + return ( + canSignIn && + !staffMembers?.some((staffMember) => { + return staffMember.user?.id === userData?.id; + }) + ); + }; return (
    @@ -139,71 +158,65 @@ const MatchesPage = async ({
    {match.teamBlue.name} {match.teamRed.name} - {match.referees ? ( - match.referees.some( - (staffMember) => - staffMember.user?.id === userData?.id, - ) ? ( -
    { - "use server"; - const cookie = - cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: - process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await signInMatchStaffMember({ - path: { - abbreviation: - tournamentAbbreviation, - matchId: match.id, - }, - query: { - in: false, - type: "REFEREE", - }, - }); - await multipleRevalidatePaths([ - "/", - `/dashboard/${tournamentAbbreviation}/qualification-rooms`, - `/tournament/${tournamentAbbreviation}/schedule`, - ]); - }} - > - -
    - ) : ( -
    - {match.referees?.map((referee) => ( - <> -
    -
    - referee -
    -
    - {referee?.user?.username} - - ))} +
    + {match.referees?.map((referee) => ( +
    +
    +
    + referee +
    +
    + {referee?.user?.username}
    - ) - ) : canSignIn ? ( + ))} +
    + {showSignOut(match.referees) && ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: false, + type: "REFEREE", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + )} + {showSignIn(match.referees) && (
    { "use server"; @@ -243,76 +256,68 @@ const MatchesPage = async ({ sign in
    - ) : ( - "-" )}
    - {match.commentators ? ( - match.commentators.some( - (staffMember) => - staffMember.user?.id === userData?.id, - ) ? ( -
    { - "use server"; - const cookie = - cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: - process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await signInMatchStaffMember({ - path: { - abbreviation: - tournamentAbbreviation, - matchId: match.id, - }, - query: { - in: false, - type: "COMMENTATOR", - }, - }); - await multipleRevalidatePaths([ - "/", - `/dashboard/${tournamentAbbreviation}/qualification-rooms`, - `/tournament/${tournamentAbbreviation}/schedule`, - ]); - }} - > - -
    - ) : ( -
    - {match.commentators?.map((com) => ( - <> -
    -
    - commentator -
    -
    - {com?.user?.username} - - ))} +
    + {match.commentators?.map((com) => ( +
    +
    +
    + referee +
    +
    + {com?.user?.username}
    - ) - ) : canSignIn ? ( + ))} +
    + {showSignOut(match.commentators) && ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: false, + type: "COMMENTATOR", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + )} + {showSignIn(match.commentators) && (
    { "use server"; @@ -352,76 +357,68 @@ const MatchesPage = async ({ sign in
    - ) : ( - "-" )}
    - {match.streamers ? ( - match.streamers.some( - (staffMember) => - staffMember.user?.id === userData?.id, - ) ? ( -
    { - "use server"; - const cookie = - cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: - process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await signInMatchStaffMember({ - path: { - abbreviation: - tournamentAbbreviation, - matchId: match.id, - }, - query: { - in: false, - type: "STREAMER", - }, - }); - await multipleRevalidatePaths([ - "/", - `/dashboard/${tournamentAbbreviation}/qualification-rooms`, - `/tournament/${tournamentAbbreviation}/schedule`, - ]); - }} - > - -
    - ) : ( -
    - {match.streamers?.map((st) => ( - <> -
    -
    - streamer -
    -
    - {st?.user?.username} - - ))} +
    + {match.streamers?.map((str) => ( +
    +
    +
    + referee +
    +
    + {str?.user?.username}
    - ) - ) : canSignIn ? ( + ))} +
    + {showSignOut(match.streamers) && ( +
    { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: + process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + await signInMatchStaffMember({ + path: { + abbreviation: + tournamentAbbreviation, + matchId: match.id, + }, + query: { + in: false, + type: "STREAMER", + }, + }); + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/qualification-rooms`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + }} + > + +
    + )} + {showSignIn(match.streamers) && (
    { "use server"; @@ -461,8 +458,6 @@ const MatchesPage = async ({ sign in
    - ) : ( - "-" )}
    (edit){match.stage?.stageType}{match.teamBlue.name}{match.teamRed.name} + + {match.teamBlue.name} + + + + {match.teamRed.name} + + {match.referees?.map((referee) => (
    diff --git a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx index a60cf9f..71f0abe 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/page.tsx @@ -125,7 +125,10 @@ const TeamPage = async ({
    )} - +
    + Captain discord: + {getTeam?.captain?.user?.discordUsername} +
    Team Members:
    From 6a0ba3b430d413d102a91e3e237593be6e5c4efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Tue, 24 Sep 2024 23:11:03 +0200 Subject: [PATCH 32/37] fix: changed sign in statement to look for roles permission --- .../[tournamentAbbreviation]/matches/page.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx index 54c7423..0e706fa 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx @@ -94,10 +94,14 @@ const MatchesPage = async ({ }, }); - const canSignIn = getStaffForATournamentUser?.permissions?.some((permission) => { - const b = permission === "MATCH_STAFF_MEMBER_SIGN_IN"; - return b; - }); + const canSignIn = + getStaffForATournamentUser?.permissions?.some((permission) => { + const b = permission === "MATCH_STAFF_MEMBER_SIGN_IN"; + return b; + }) || + getStaffForATournamentUser?.roles?.some((role) => + role.permissions.some((permission) => permission === "MATCH_STAFF_MEMBER_SIGN_IN"), + ); const showSignOut = (staffMembers: StaffMemberResponseDto[] | undefined) => { return ( From dad8f2d950607a6571ed0738cc840c560cf7f921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Thu, 26 Sep 2024 20:42:39 +0200 Subject: [PATCH 33/37] fix: display match id on schedule page --- .../(tournament)/tournament/[tournamentId]/schedule/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx index 04916e4..7c7c6e3 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx @@ -36,6 +36,7 @@ const SingleTournamentSchedule = async ({
    + @@ -53,6 +54,7 @@ const SingleTournamentSchedule = async ({ }) .map((match) => ( + From ff436cfd5f1e8c1636f6c1773cd8a2d259d51116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Sat, 28 Sep 2024 01:10:50 +0200 Subject: [PATCH 34/37] feat: Added match ID to dashboard --- .../dashboard/[tournamentAbbreviation]/matches/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx index 0e706fa..9fd82a8 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx @@ -137,6 +137,7 @@ const MatchesPage = async ({
    Match ID Start date time (UTC+0) Stage Team blue
    {match.matchId} {format(new Date(match.startDate), "dd/MM/yyyy HH:mm")}
    + @@ -155,6 +156,7 @@ const MatchesPage = async ({ }) .map((match) => ( + From c6a7da3d1011bfa248e0e008ee070765dc8f4e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Or=C5=82owski?= Date: Mon, 30 Sep 2024 22:14:39 +0200 Subject: [PATCH 35/37] feat: Match ID in modal --- src/actions/admin/adminAddMatchActions.ts | 1 + .../dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx | 1 + src/formSchemas/addMachSchema.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/actions/admin/adminAddMatchActions.ts b/src/actions/admin/adminAddMatchActions.ts index 7b128fa..86d7fe5 100644 --- a/src/actions/admin/adminAddMatchActions.ts +++ b/src/actions/admin/adminAddMatchActions.ts @@ -23,6 +23,7 @@ export async function addMatchAction(formData: AddMachSchemaType) { }, body: { startDate: formData.dataTimeStart, + matchId: formData.matchId, commentatorIds: formData.commentatorIds, refereeIds: formData.refereeIds, streamerIds: formData.streamerIds, diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx index f751515..da6ee96 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx @@ -153,6 +153,7 @@ export const MatchesModal = ({ type={"hidden"} required={true} /> + Date: Wed, 10 Dec 2025 09:34:04 +0100 Subject: [PATCH 36/37] feat: added StageNavigation component and team management modals - Added `StageNavigation` component for tournament stage links. - Implemented `EditTeamModal` and `DeleteTeamModal` for team management. - Added actions for rescheduling matches, deleting participants, and updating teams. --- .idea/misc.xml | 7 - package-lock.json | 42 +- src/actions/admin/adminBeatMapActions.ts | 17 +- src/actions/admin/adminDeleteMatchActions.ts | 46 ++ src/actions/admin/adminEditMatchActions.ts | 52 ++ src/actions/admin/adminTeamActions.ts | 283 +++++++++++ src/actions/public/createTeamAction.ts | 79 +-- src/actions/public/decideRescheduleAction.ts | 49 ++ .../public/deleteParticipantFromTeamAction.ts | 47 ++ src/actions/public/disbandTeamAction.ts | 44 ++ src/actions/public/getStaffMembersAction.ts | 39 ++ src/actions/public/getTournamentAction.ts | 39 ++ src/actions/public/rescheduleMatchAction.ts | 46 ++ .../[stageType]/[mappoolId]/AddBeatMap.tsx | 24 +- .../mappool/[stageType]/[mappoolId]/page.tsx | 2 +- .../matches/EditMatchModal.tsx | 199 ++++++++ .../matches/MatchesModal.tsx | 49 +- .../matches/RescheduleMatchModal.tsx | 136 ++++++ .../[tournamentAbbreviation]/matches/page.tsx | 41 +- .../settings/page.tsx | 194 ++++++-- .../teams/DeleteTeamModal.tsx | 149 ++++++ .../teams/EditTeamModal.tsx | 455 ++++++++++++++++++ .../teams/RemoveParticipantModal.tsx | 73 +++ .../teams/TeamsTable.tsx | 192 ++++++++ .../[tournamentAbbreviation]/teams/page.tsx | 233 ++------- .../mappool/[stageType]/page.tsx | 118 +++-- .../[tournamentId]/schedule/page.tsx | 16 + .../teams/[teamId]/ChangeTeamNameForm.tsx | 157 +++++- .../[teamId]/DeleteParticipantButton.tsx | 62 +++ .../teams/[teamId]/DisbandTeamButton.tsx | 56 +++ .../[teamId]/InvitePlayerToTeamButton.tsx | 6 + .../[tournamentId]/teams/[teamId]/page.tsx | 108 ++--- src/app/layout.tsx | 2 +- src/formSchemas/editMatchSchema.ts | 14 + src/formSchemas/rescheduleMatchSchema.ts | 22 + src/formSchemas/updateTeamSchema.ts | 7 +- src/lib/helpers.ts | 6 +- src/lib/restClient.ts | 2 +- src/ui/atoms/AvatarGroup/AvatarGroup.tsx | 60 +++ .../Forms/Select/StaffMemberAutocomplete.tsx | 207 ++++++++ .../BeatmapListItem/BeatmapListItem.tsx | 217 +++++++++ src/ui/molecules/Cards/TeamCard.tsx | 9 +- src/ui/organisms/Footer/Footer.tsx | 6 +- .../StageNavigation/StageNavigation.tsx | 64 +++ 44 files changed, 3216 insertions(+), 460 deletions(-) delete mode 100644 .idea/misc.xml create mode 100644 src/actions/admin/adminDeleteMatchActions.ts create mode 100644 src/actions/admin/adminEditMatchActions.ts create mode 100644 src/actions/admin/adminTeamActions.ts create mode 100644 src/actions/public/decideRescheduleAction.ts create mode 100644 src/actions/public/deleteParticipantFromTeamAction.ts create mode 100644 src/actions/public/disbandTeamAction.ts create mode 100644 src/actions/public/getStaffMembersAction.ts create mode 100644 src/actions/public/getTournamentAction.ts create mode 100644 src/actions/public/rescheduleMatchAction.ts create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/EditMatchModal.tsx create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/RescheduleMatchModal.tsx create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/DeleteTeamModal.tsx create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/EditTeamModal.tsx create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/RemoveParticipantModal.tsx create mode 100644 src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/TeamsTable.tsx create mode 100644 src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DeleteParticipantButton.tsx create mode 100644 src/app/(tournament)/tournament/[tournamentId]/teams/[teamId]/DisbandTeamButton.tsx create mode 100644 src/formSchemas/editMatchSchema.ts create mode 100644 src/formSchemas/rescheduleMatchSchema.ts create mode 100644 src/ui/atoms/AvatarGroup/AvatarGroup.tsx create mode 100644 src/ui/atoms/Forms/Select/StaffMemberAutocomplete.tsx create mode 100644 src/ui/molecules/BeatmapListItem/BeatmapListItem.tsx create mode 100644 src/ui/organisms/StageNavigation/StageNavigation.tsx diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 30bab2a..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 292cea0..2ff4864 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1255,6 +1255,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.14.0", "@typescript-eslint/types": "6.14.0", @@ -1405,6 +1406,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1774,6 +1776,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001565", "electron-to-chromium": "^1.4.601", @@ -2352,7 +2355,8 @@ "node_modules/embla-carousel": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.1.3.tgz", - "integrity": "sha512-GiRpKtzidV3v50oVMly8S+D7iE1r96ttt7fSlvtyKHoSkzrAnVcu8fX3c4j8Ol2hZSQlVfDqDIqdrFPs0u5TWQ==" + "integrity": "sha512-GiRpKtzidV3v50oVMly8S+D7iE1r96ttt7fSlvtyKHoSkzrAnVcu8fX3c4j8Ol2hZSQlVfDqDIqdrFPs0u5TWQ==", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.1.3", @@ -2546,6 +2550,7 @@ "version": "8.49.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2703,6 +2708,7 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -4485,6 +4491,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", "integrity": "sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "14.2.4", "@swc/helpers": "0.5.5", @@ -5027,6 +5034,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -5167,6 +5175,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -5333,6 +5342,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -5362,6 +5372,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5416,6 +5427,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.3", "use-sync-external-store": "^1.0.0" @@ -5467,7 +5479,8 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6439,6 +6452,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7403,6 +7417,7 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", + "peer": true, "requires": { "@typescript-eslint/scope-manager": "6.14.0", "@typescript-eslint/types": "6.14.0", @@ -7478,7 +7493,8 @@ "acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==" + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -7726,6 +7742,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001565", "electron-to-chromium": "^1.4.601", @@ -8118,7 +8135,8 @@ "embla-carousel": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.1.3.tgz", - "integrity": "sha512-GiRpKtzidV3v50oVMly8S+D7iE1r96ttt7fSlvtyKHoSkzrAnVcu8fX3c4j8Ol2hZSQlVfDqDIqdrFPs0u5TWQ==" + "integrity": "sha512-GiRpKtzidV3v50oVMly8S+D7iE1r96ttt7fSlvtyKHoSkzrAnVcu8fX3c4j8Ol2hZSQlVfDqDIqdrFPs0u5TWQ==", + "peer": true }, "embla-carousel-react": { "version": "8.1.3", @@ -8272,6 +8290,7 @@ "version": "8.49.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8391,6 +8410,7 @@ "version": "2.29.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "peer": true, "requires": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -9621,6 +9641,7 @@ "version": "14.2.4", "resolved": "https://registry.npmjs.org/next/-/next-14.2.4.tgz", "integrity": "sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==", + "peer": true, "requires": { "@next/env": "14.2.4", "@next/swc-darwin-arm64": "14.2.4", @@ -9967,6 +9988,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -10045,7 +10067,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", - "dev": true + "dev": true, + "peer": true }, "prettier-plugin-tailwindcss": { "version": "0.5.9", @@ -10118,6 +10141,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -10138,6 +10162,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -10174,6 +10199,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "peer": true, "requires": { "@types/use-sync-external-store": "^0.0.3", "use-sync-external-store": "^1.0.0" @@ -10208,7 +10234,8 @@ "redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "peer": true }, "redux-thunk": { "version": "3.1.0", @@ -10879,7 +10906,8 @@ "typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "peer": true }, "ufo": { "version": "1.5.4", diff --git a/src/actions/admin/adminBeatMapActions.ts b/src/actions/admin/adminBeatMapActions.ts index ecbbfd6..bf968ce 100644 --- a/src/actions/admin/adminBeatMapActions.ts +++ b/src/actions/admin/adminBeatMapActions.ts @@ -1,10 +1,10 @@ "use server"; import { cookies } from "next/headers"; -import { addBeatmap, client, type modification } from "../../../client"; +import { addBeatmap, client, type modification, type stageType } from "../../../client"; import { type AddBeatMapSchemaType } from "@/formSchemas/addBeatMapSchema"; -// import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; -export async function addBeatMapAction(formData: AddBeatMapSchemaType) { +export async function addBeatMapAction(formData: AddBeatMapSchemaType, stageType: stageType) { "use server"; const cookie = cookies().get("JWT")?.value; // configure internal service client @@ -36,12 +36,11 @@ export async function addBeatMapAction(formData: AddBeatMapSchemaType) { }; } - // await multipleRevalidatePaths([ - // "/", - // `/dashboard/${formData.tournamentAbb}/mappool/${formData.mappoolId}`, - // `/tournament/${formData.tournamentAbb}/mappool/${formData.mappoolId}`, - // ]); - //todo: revalidate paths + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbb}/mappool/${stageType}/${formData.mappoolId}`, + `/tournament/${formData.tournamentAbb}/mappool/${stageType}`, + ]); return { status: true as const, diff --git a/src/actions/admin/adminDeleteMatchActions.ts b/src/actions/admin/adminDeleteMatchActions.ts new file mode 100644 index 0000000..9754e28 --- /dev/null +++ b/src/actions/admin/adminDeleteMatchActions.ts @@ -0,0 +1,46 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, deleteMatch } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; + +export async function deleteMatchAction(tournamentAbbreviation: string, matchId: string) { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { error } = await deleteMatch({ + path: { + abbreviation: tournamentAbbreviation, + matchId: matchId, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/matches`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + + return { + status: true as const, + }; +} + + + + diff --git a/src/actions/admin/adminEditMatchActions.ts b/src/actions/admin/adminEditMatchActions.ts new file mode 100644 index 0000000..de87798 --- /dev/null +++ b/src/actions/admin/adminEditMatchActions.ts @@ -0,0 +1,52 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, updateMatch, type MatchRequestDto } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import { type EditMatchSchemaType } from "@/formSchemas/editMatchSchema"; + +export async function editMatchAction(formData: EditMatchSchemaType) { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + const { data, error } = await updateMatch({ + path: { + abbreviation: formData.tournamentAbbreviation, + matchId: formData.matchId, + }, + body: { + startDate: formData.dataTimeStart, + stageType: formData.stageType as MatchRequestDto["stageType"], + commentatorIds: formData.commentatorIds ?? [], + refereeIds: formData.refereeIds ?? [], + streamerIds: formData.streamerIds ?? [], + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/matches`, + `/tournament/${formData.tournamentAbbreviation}/schedule`, + ]); + + return { + status: true as const, + response: data, + }; +} + diff --git a/src/actions/admin/adminTeamActions.ts b/src/actions/admin/adminTeamActions.ts new file mode 100644 index 0000000..559dcf4 --- /dev/null +++ b/src/actions/admin/adminTeamActions.ts @@ -0,0 +1,283 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, addParticipantToTeam, removeParticipantFromTeam, changeTeamCaptain, deleteTeam, changeTeamStatus } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; + +export async function updateTeamAction(formDataToSend: FormData) { + "use server"; + const cookie = cookies().get("JWT")?.value; + const apiUrl = process.env.NEXT_PUBLIC_API_URL; + + if (!apiUrl) { + return { + status: false as const, + errorMessage: "API URL is not configured", + }; + } + + const tournamentAbbreviation = formDataToSend.get("tournamentAbbreviation") as string; + const teamId = formDataToSend.get("teamId") as string; + const name = formDataToSend.get("name") as string; + + if (!tournamentAbbreviation || !teamId || !name) { + return { + status: false as const, + errorMessage: "Missing required fields", + }; + } + + // Create new FormData with only the fields needed for the API + const apiFormData = new FormData(); + apiFormData.append("name", name); + const logo = formDataToSend.get("logo"); + if (logo instanceof File && logo.size > 0) { + apiFormData.append("logo", logo); + } + + // Use fetch directly for multipart/form-data + const response = await fetch( + `${apiUrl}/admin/tournaments/${tournamentAbbreviation}/teams/${teamId}`, + { + method: "PATCH", + headers: { + Cookie: `token=${cookie}`, + }, + body: apiFormData, + } + ); + + if (!response.ok) { + const errorText = await response.text(); + let errorData; + try { + errorData = JSON.parse(errorText); + } catch { + errorData = { errors: [errorText || "Failed to update team"] }; + } + return { + status: false as const, + errorMessage: errorData.errors?.map((e: string) => e).join(", ") || errorData.message || "Failed to update team", + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + `/account/`, + ]); + + return { + status: true as const, + }; +} + +export async function addParticipantAction( + tournamentAbbreviation: string, + teamId: string, + osuId: string, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { data, error } = await addParticipantToTeam({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + osuId: osuId, + }, + }); + + if (error) { + const errorMessage: string = error.errors?.join(", ") || "Failed to add participant"; + return { + status: false as const, + errorMessage, + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + ]); + + return { + status: true as const, + team: data, + }; +} + +export async function removeParticipantAction( + tournamentAbbreviation: string, + teamId: string, + osuId: string, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { data, error } = await removeParticipantFromTeam({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + osuId: osuId, + }, + }); + + if (error) { + const errorMessage: string = error.errors?.join(", ") || "Failed to remove participant"; + return { + status: false as const, + errorMessage, + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + ]); + + return { + status: true as const, + team: data, + }; +} + +export async function changeCaptainAction( + tournamentAbbreviation: string, + teamId: string, + osuId: string, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { data, error } = await changeTeamCaptain({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + osuId: osuId, + }, + }); + + if (error) { + const errorMessage: string = error.errors?.join(", ") || "Failed to change captain"; + return { + status: false as const, + errorMessage, + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + ]); + + return { + status: true as const, + team: data, + }; +} + +export async function deleteTeamAction( + tournamentAbbreviation: string, + teamId: string, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { error } = await deleteTeam({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + }, + }); + + if (error) { + const errorMessage: string = error.errors?.join(", ") || "Failed to delete team"; + return { + status: false as const, + errorMessage, + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/account/`, + ]); + + return { + status: true as const, + }; +} + +export async function changeTeamStatusAction( + tournamentAbbreviation: string, + teamId: string, + status: "ACCEPTED" | "REJECTED", +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { error } = await changeTeamStatus({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + }, + query: { + status: status, + }, + }); + + if (error) { + const errorMessage: string = error.errors?.join(", ") || "Failed to change team status"; + return { + status: false as const, + errorMessage, + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + `/account/`, + ]); + + return { + status: true as const, + }; +} diff --git a/src/actions/public/createTeamAction.ts b/src/actions/public/createTeamAction.ts index 74c09ec..84a43b6 100644 --- a/src/actions/public/createTeamAction.ts +++ b/src/actions/public/createTeamAction.ts @@ -1,9 +1,8 @@ "use server"; import { cookies } from "next/headers"; -import { client, createTeam, inviteParticipant, updateTeam } from "../../../client"; +import { client, createTeam, inviteParticipant } from "../../../client"; import { type CreateTeamSchemaType } from "@/formSchemas/createTeamSchema"; -import { type updateTeamSchemaType } from "@/formSchemas/updateTeamSchema"; import { type inviteToTeamSchemaType } from "@/formSchemas/inviteToTeamSchema"; import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; @@ -57,45 +56,71 @@ export async function createTeamAction(formData: CreateTeamSchemaType) { }; } -export async function updateTeamAction(formData: updateTeamSchemaType) { +export async function updateTeamAction(formDataToSend: FormData) { "use server"; const cookie = cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - const { data, error } = await updateTeam({ - path: { - abbreviation: formData.tournamentAbbreviation, - teamId: formData.teamId, - }, - body: { - name: formData.name, - logoUrl: formData.logoUrl, - }, - }); + const apiUrl = process.env.NEXT_PUBLIC_API_URL; + + if (!apiUrl) { + return { + status: false as const, + errorMessage: "API URL is not configured", + }; + } - if (error) { + const tournamentAbbreviation = formDataToSend.get("tournamentAbbreviation") as string; + const teamId = formDataToSend.get("teamId") as string; + const name = formDataToSend.get("name") as string; + + if (!tournamentAbbreviation || !teamId || !name) { return { status: false as const, - errorMessage: error.errors?.map((e) => e).join(", "), + errorMessage: "Missing required fields", + }; + } + + // Create new FormData with only the fields needed for the API + const apiFormData = new FormData(); + apiFormData.append("name", name); + const logo = formDataToSend.get("logo"); + if (logo instanceof File && logo.size > 0) { + apiFormData.append("logo", logo); + } + + // Use fetch directly for multipart/form-data + const response = await fetch( + `${apiUrl}/tournaments/${tournamentAbbreviation}/teams/${teamId}`, + { + method: "PUT", + headers: { + Cookie: `token=${cookie}`, + }, + body: apiFormData, + } + ); + + if (!response.ok) { + const errorText = await response.text(); + let errorData; + try { + errorData = JSON.parse(errorText); + } catch { + errorData = { errors: [errorText || "Failed to update team"] }; + } + return { + status: false as const, + errorMessage: errorData.errors?.map((e: string) => e).join(", ") || errorData.message || "Failed to update team", }; } await multipleRevalidatePaths([ "/", - `/tournament/${formData.tournamentAbbreviation}/teams/${formData.teamId}`, - `/tournament/${formData.tournamentAbbreviation}`, + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + `/tournament/${tournamentAbbreviation}`, ]); return { status: true as const, - response: data, }; } diff --git a/src/actions/public/decideRescheduleAction.ts b/src/actions/public/decideRescheduleAction.ts new file mode 100644 index 0000000..3c9020c --- /dev/null +++ b/src/actions/public/decideRescheduleAction.ts @@ -0,0 +1,49 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, decideMatchReschedule } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; + +export async function decideRescheduleAction( + tournamentAbbreviation: string, + matchId: string, + isAccepted: boolean, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + }); + const { data, error } = await decideMatchReschedule({ + path: { + abbreviation: tournamentAbbreviation, + }, + body: { + matchId: matchId, + isAccepted: isAccepted, + }, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", ") || "Failed to decide on reschedule", + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${tournamentAbbreviation}/matches`, + `/tournament/${tournamentAbbreviation}/schedule`, + ]); + + return { + status: true as const, + response: data, + }; +} + + diff --git a/src/actions/public/deleteParticipantFromTeamAction.ts b/src/actions/public/deleteParticipantFromTeamAction.ts new file mode 100644 index 0000000..f8ed3a7 --- /dev/null +++ b/src/actions/public/deleteParticipantFromTeamAction.ts @@ -0,0 +1,47 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, deleteParticipantFromTeam } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; + +export async function deleteParticipantFromTeamAction( + tournamentAbbreviation: string, + teamId: string, + participantId: string, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { error } = await deleteParticipantFromTeam({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + participantId: participantId, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", ") || "Failed to remove participant", + }; + } + + await multipleRevalidatePaths([ + `/tournament/${tournamentAbbreviation}/teams/${teamId}`, + `/tournament/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}`, + "/", + ]); + + return { + status: true as const, + }; +} + diff --git a/src/actions/public/disbandTeamAction.ts b/src/actions/public/disbandTeamAction.ts new file mode 100644 index 0000000..a17c951 --- /dev/null +++ b/src/actions/public/disbandTeamAction.ts @@ -0,0 +1,44 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, disbandTeam } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; + +export async function disbandTeamAction( + tournamentAbbreviation: string, + teamId: string, +) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { error } = await disbandTeam({ + path: { + abbreviation: tournamentAbbreviation, + teamId: teamId, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", ") || "Failed to disband team", + }; + } + + await multipleRevalidatePaths([ + `/tournament/${tournamentAbbreviation}/teams`, + `/tournament/${tournamentAbbreviation}`, + "/", + ]); + + return { + status: true as const, + }; +} + diff --git a/src/actions/public/getStaffMembersAction.ts b/src/actions/public/getStaffMembersAction.ts new file mode 100644 index 0000000..f7bdea4 --- /dev/null +++ b/src/actions/public/getStaffMembersAction.ts @@ -0,0 +1,39 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, getStaffMembers1 } from "../../../client"; +import type { selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; + +export async function getStaffMembersAction( + tournamentAbbreviation: string, +): Promise { + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { data } = await getStaffMembers1({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + + const staffMemberOptions: selectOptions[] = + data + ?.filter((s) => s.user) + ?.map((staffMember) => ({ + id: staffMember.id, + label: staffMember.user + ? staffMember.user.username + : staffMember.username || "", + })) || []; + + return staffMemberOptions; +} + + + + diff --git a/src/actions/public/getTournamentAction.ts b/src/actions/public/getTournamentAction.ts new file mode 100644 index 0000000..b53ce88 --- /dev/null +++ b/src/actions/public/getTournamentAction.ts @@ -0,0 +1,39 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, getTournamentByAbbreviation, type TournamentResponseDto } from "../../../client"; + +export async function getTournamentAction( + abbreviation: string, +): Promise<{ status: boolean; data?: TournamentResponseDto; errorMessage?: string }> { + "use server"; + const cookie = cookies().get("JWT")?.value; + // configure internal service client + client.setConfig({ + // set default base url for requests + baseUrl: process.env.NEXT_PUBLIC_API_URL, + // set default headers for requests + headers: { + Cookie: `token=${cookie}`, + }, + }); + + const { data, error } = await getTournamentByAbbreviation({ + path: { + abbreviation, + }, + }); + + if (error) { + return { + status: false, + errorMessage: error.errors?.map((e) => e).join(", "), + }; + } + + return { + status: true, + data, + }; +} + diff --git a/src/actions/public/rescheduleMatchAction.ts b/src/actions/public/rescheduleMatchAction.ts new file mode 100644 index 0000000..d7668f4 --- /dev/null +++ b/src/actions/public/rescheduleMatchAction.ts @@ -0,0 +1,46 @@ +"use server"; + +import { cookies } from "next/headers"; +import { client, rescheduleMatch } from "../../../client"; +import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import { type RescheduleMatchSchemaType } from "@/formSchemas/rescheduleMatchSchema"; + +export async function rescheduleMatchAction(formData: RescheduleMatchSchemaType) { + "use server"; + const cookie = cookies().get("JWT")?.value; + client.setConfig({ + baseUrl: process.env.NEXT_PUBLIC_API_URL, + }); + const { data, error } = await rescheduleMatch({ + path: { + abbreviation: formData.tournamentAbbreviation, + }, + body: { + matchId: formData.matchId, + proposedDateTime: formData.proposedDateTime, + }, + headers: { + Cookie: `token=${cookie}`, + }, + }); + + if (error) { + return { + status: false as const, + errorMessage: error.errors?.map((e) => e).join(", ") || "Failed to reschedule match", + }; + } + + await multipleRevalidatePaths([ + "/", + `/dashboard/${formData.tournamentAbbreviation}/matches`, + `/tournament/${formData.tournamentAbbreviation}/schedule`, + ]); + + return { + status: true as const, + response: data, + }; +} + + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx index e81a388..898167e 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/AddBeatMap.tsx @@ -1,7 +1,8 @@ "use client"; import React, { useRef } from "react"; +import { useRouter } from "next/navigation"; import { toast } from "sonner"; -import { modification } from "../../../../../../../../../client"; +import { modification, type stageType } from "../../../../../../../../../client"; import { Button } from "@ui/atoms/Button/Button"; import Modal from "@ui/organisms/Modal/Modal"; import { ComboBox } from "@ui/atoms/Forms/Select/ComboBox"; @@ -14,27 +15,42 @@ import { addBeatMapAction } from "@/actions/admin/adminBeatMapActions"; interface IAddStageFormProps { tournamentAbb: string; mappoolId: string; + stageType: stageType; } -export const AddBeatMap = ({ tournamentAbb, mappoolId }: IAddStageFormProps) => { +export const AddBeatMap = ({ tournamentAbb, mappoolId, stageType }: IAddStageFormProps) => { const modalRef = useRef(null); const formRef = React.useRef(null); + const router = useRouter(); const [state, formAction] = useTypeSafeFormState(addBeatMapSchema, async (data) => { - const addBeatMapResponse = await addBeatMapAction(data); + const addBeatMapResponse = await addBeatMapAction(data, stageType); if (!addBeatMapResponse.status) { return toast.error(addBeatMapResponse.errorMessage, { duration: 3000, }); } + toast.success("Beatmap added successfully", { + duration: 3000, + }); modalRef.current?.close(); resetFormValues({ formRef, resetWithoutInputNames: ["tournamentAbb", "mappoolId"], schema: addBeatMapSchema, }); + router.refresh(); }); + const handleOpenModal = () => { + resetFormValues({ + formRef, + resetWithoutInputNames: ["tournamentAbb", "mappoolId"], + schema: addBeatMapSchema, + }); + modalRef?.current?.showModal(); + }; + const modificationTypeSelectOptions = [ { id: modification.DT, @@ -76,7 +92,7 @@ export const AddBeatMap = ({ tournamentAbb, mappoolId }: IAddStageFormProps) => return ( <> - diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx index d06784c..1f18a2e 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/mappool/[stageType]/[mappoolId]/page.tsx @@ -72,7 +72,7 @@ const StageTypePage = async ({ {getMappoolData.isReleased ? "Unrelease" : "Release"} mappool - +
    Match ID Start date time (UTC+0) Stage Team blue
    {match.matchId} {format(new Date(match.startDate), "dd/MM/yyyy HH:mm")}
    {/* head */} diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/EditMatchModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/EditMatchModal.tsx new file mode 100644 index 0000000..de53b6c --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/EditMatchModal.tsx @@ -0,0 +1,199 @@ +"use client"; +import React, { useRef, useEffect } from "react"; +import { toast } from "sonner"; +import type { MatchResponseDto } from "../../../../../../../client"; +import { Button } from "@ui/atoms/Button/Button"; +import Modal from "@ui/organisms/Modal/Modal"; +import { StaffMemberAutocomplete } from "@ui/atoms/Forms/Select/StaffMemberAutocomplete"; +import type { selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; +import { Input } from "@ui/atoms/Forms/Input/Input"; +import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; +import { resetFormValues } from "@/lib/helpers"; +import { editMatchSchema } from "@/formSchemas/editMatchSchema"; +import { editMatchAction } from "@/actions/admin/adminEditMatchActions"; + +type EditMatchModalType = { + match: MatchResponseDto; + staffMembers: selectOptions[]; +}; + +interface IEditMatchModalProps { + tournamentAbb: string; + modalType: EditMatchModalType; +} + +export const EditMatchModal = ({ + tournamentAbb, + modalType, +}: IEditMatchModalProps) => { + const modalRef = useRef(null); + const formRef = React.useRef(null); + + // Format date for datetime-local input (YYYY-MM-DDTHH:mm) + const formatDateForInput = (dateString: string | null | undefined): string => { + if (!dateString) { + return ""; + } + const date = new Date(dateString); + // Check if date is valid + if (isNaN(date.getTime())) { + return ""; + } + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + return `${year}-${month}-${day}T${hours}:${minutes}`; + }; + + const [stateEditMatch, formActionEditMatch] = useTypeSafeFormState( + editMatchSchema, + async (data) => { + const editMatchResponse = await editMatchAction(data); + if (!editMatchResponse.status) { + return toast.error(editMatchResponse.errorMessage, { + duration: 3000, + }); + } + toast.success("Match updated successfully", { + duration: 3000, + }); + modalRef.current?.close(); + resetFormValues({ + formRef, + resetWithoutInputNames: ["tournamentAbbreviation", "matchId", "dataTimeStart"], + schema: editMatchSchema, + }); + }, + ); + + // Function to update date input value + const updateDateInput = React.useCallback(() => { + if (formRef.current) { + const dateInput = formRef.current.querySelector( + 'input[name="dataTimeStart"]', + ); + if (dateInput) { + dateInput.value = formatDateForInput(modalType.match.startDate); + } + } + }, [modalType.match.startDate]); + + // Update input value when modal opens + const handleOpenModal = () => { + modalRef.current?.showModal(); + // Use setTimeout to ensure modal is fully opened before updating + // Using requestAnimationFrame for better timing + requestAnimationFrame(() => { + requestAnimationFrame(() => { + updateDateInput(); + }); + }); + }; + + // Also update when match changes + useEffect(() => { + updateDateInput(); + }, [modalType.match.id, updateDateInput]); + + return ( + <> + + + +

    Edit match

    +
    +
    + + + + + referee.id) || [] + } + errorMessage={ + stateEditMatch?.errors.refereeIds && + stateEditMatch?.errors.refereeIds[0] + } + /> + + commentator.id) || [] + } + errorMessage={ + stateEditMatch?.errors.commentatorIds && + stateEditMatch?.errors.commentatorIds[0] + } + /> + + streamer.id) || [] + } + errorMessage={ + stateEditMatch?.errors.streamerIds && + stateEditMatch?.errors.streamerIds[0] + } + /> +
    + + +
    + + ); +}; + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx index da6ee96..e7bab7f 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal.tsx @@ -5,6 +5,7 @@ import { stageType } from "../../../../../../../client"; import { Button } from "@ui/atoms/Button/Button"; import Modal from "@ui/organisms/Modal/Modal"; import { ComboBox, type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; +import { StaffMemberAutocomplete } from "@ui/atoms/Forms/Select/StaffMemberAutocomplete"; import { Input } from "@ui/atoms/Forms/Input/Input"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; import { editQualificationRoomsAction } from "@/actions/admin/adminQualificationRoomsActions"; @@ -236,61 +237,37 @@ export const MatchesModal = ({ // } // readonly={modalType.type === "edit" ? true : false} /> - staff.id) - : undefined + : [] } - // type={modalType.type === "edit" ? undefined : "hidden"} - // disabled={modalType.type === "add"} - // hidden={modalType.type === "add"} - multiple={true} - // errorMessage={ - // stateEditQRoom?.errors.rosterIds && - // stateEditQRoom?.errors.rosterIds[0] - // } /> - staff.id) - : undefined + : [] } - // type={modalType.type === "edit" ? undefined : "hidden"} - // disabled={modalType.type === "add"} - // hidden={modalType.type === "add"} - multiple={true} - // errorMessage={ - // stateEditQRoom?.errors.rosterIds && - // stateEditQRoom?.errors.rosterIds[0] - // } /> - staff.id) - : undefined + : [] } - // type={modalType.type === "edit" ? undefined : "hidden"} - // disabled={modalType.type === "add"} - // hidden={modalType.type === "add"} - multiple={true} - // errorMessage={ - // stateEditQRoom?.errors.rosterIds && - // stateEditQRoom?.errors.rosterIds[0] - // } /> + + + ); +}; + +export const RescheduleMatchModal = ({ + tournamentAbb, + matchId, + currentMatchDate, +}: RescheduleMatchModalProps) => { + const modalRef = useRef(null); + const formRef = React.useRef(null); + + const [state, formAction] = useTypeSafeFormState( + rescheduleMatchSchema, + async (data) => { + const response = await rescheduleMatchAction(data); + if (!response.status) { + return toast.error(response.errorMessage, { + duration: 3000, + }); + } + toast.success("Reschedule request sent successfully", { + duration: 3000, + }); + modalRef.current?.close(); + formRef.current?.reset(); + }, + ); + + return ( + <> + + + +

    Reschedule Match

    +
    +
    + + + + + {state?.errors.agreeToRules && ( +

    {state.errors.agreeToRules[0]}

    + )} +
    +
    + { + modalRef.current?.close(); + formRef.current?.reset(); + }} + /> +
    + +
    + + ); +}; + + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx index 9fd82a8..a08e8d4 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/page.tsx @@ -18,6 +18,8 @@ import { type selectOptions } from "@ui/atoms/Forms/Select/ComboBox"; import { getUser } from "@/actions/public/getUserAction"; import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; import { MatchesModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/MatchesModal"; +import { EditMatchModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/EditMatchModal"; +import { deleteMatchAction } from "@/actions/admin/adminDeleteMatchActions"; const MatchesPage = async ({ params: { tournamentAbbreviation }, @@ -103,6 +105,9 @@ const MatchesPage = async ({ role.permissions.some((permission) => permission === "MATCH_STAFF_MEMBER_SIGN_IN"), ); + const isHost = + getStaffForATournamentUser?.roles?.some((role) => role.name === "Host") || false; + const showSignOut = (staffMembers: StaffMemberResponseDto[] | undefined) => { return ( staffMembers?.some((staffMember) => { @@ -120,6 +125,7 @@ const MatchesPage = async ({ ); }; + return (

    Matches

    @@ -145,7 +151,7 @@ const MatchesPage = async ({
    - + {isHost && } @@ -466,7 +472,38 @@ const MatchesPage = async ({ )} - + {isHost && ( + + )} ))} diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx index 0ec363c..a9d24e8 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/settings/page.tsx @@ -3,7 +3,8 @@ import React, { useEffect } from "react"; import { useParams, useRouter } from "next/navigation"; import { toast } from "sonner"; import ReactQuill from "react-quill"; -import { getTournamentByAbbreviation, type TournamentResponseDto } from "../../../../../../../client"; +import Image from "next/image"; +import { type TournamentResponseDto } from "../../../../../../../client"; import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; import { editTournamentSchema } from "@/formSchemas/editTournamentSchema"; import { Input } from "@ui/atoms/Forms/Input/Input"; @@ -11,15 +12,16 @@ import { Button } from "@ui/atoms/Button/Button"; import "react-quill/dist/quill.snow.css"; import { editTournamentAction } from "@/actions/admin/editTournamentAction"; import { editTournamentImageAction } from "@/actions/admin/editTournamentImageAction"; +import { getTournamentAction } from "@/actions/public/getTournamentAction"; const SettingsPage = () => { const [tournamentData, setTournamentData] = React.useState(null); const { tournamentAbbreviation } = useParams<{ tournamentAbbreviation: string }>(); - const [image, setImage] = React.useState( - undefined, - ); + const [selectedImage, setSelectedImage] = React.useState(null); + const [imagePreview, setImagePreview] = React.useState(null); const formRef = React.useRef(null); const [rulesError, setRulesError] = React.useState(null); + const [rules, setRules] = React.useState(""); const router = useRouter(); const modules = { @@ -35,11 +37,12 @@ const SettingsPage = () => { const [editTournamentState, editTournamentFormAction] = useTypeSafeFormState( editTournamentSchema, async (data) => { - if (!tournamentData?.rules) { + if (!rules) { setRulesError("Rules are required"); return; } - const editTournamentResponse = await editTournamentAction(data, tournamentData?.rules); + setRulesError(null); + const editTournamentResponse = await editTournamentAction(data, rules); if (!editTournamentResponse.status) { return toast.error(editTournamentResponse.errorMessage, { duration: 3000, @@ -55,16 +58,37 @@ const SettingsPage = () => { useEffect(() => { const fetchTournamentData = async () => { - const { data: tournamentData } = await getTournamentByAbbreviation({ - path: { - abbreviation: tournamentAbbreviation, - }, - }); - setTournamentData(tournamentData || null); + const result = await getTournamentAction(tournamentAbbreviation); + if (result.status && result.data) { + setTournamentData(result.data); + setRules(result.data.rules || ""); + } else { + toast.error(result.errorMessage || "Failed to load tournament data", { + duration: 3000, + }); + } }; void fetchTournamentData(); }, [tournamentAbbreviation]); + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) { + setSelectedImage(null); + setImagePreview(null); + return; + } + + setSelectedImage(file); + + // Create preview + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); + }; + return (

    Settings

    @@ -86,7 +110,13 @@ const SettingsPage = () => { editTournamentState?.errors.abbreviation[0] } required={true} - defaultValue={tournamentData?.abbreviation} + value={tournamentData?.abbreviation || ""} + onChange={(e) => { + setTournamentData((prev) => ({ + ...prev, + abbreviation: e.target.value, + } as TournamentResponseDto)); + }} /> { editTournamentState?.errors.name && editTournamentState?.errors.name[0] } required={true} - defaultValue={tournamentData?.name} + value={tournamentData?.name || ""} + onChange={(e) => { + setTournamentData((prev) => ({ + ...prev, + name: e.target.value, + } as TournamentResponseDto)); + }} />

    Prize pool

    { + const prize = e.target.value ? Number(e.target.value) : undefined; + setTournamentData((prev) => { + const prizePool = [...(prev?.prizePool || [])]; + if (prizePool[0]) { + prizePool[0] = { ...prizePool[0], prize: prize || 0 }; + } else { + prizePool[0] = { type: 0, prize: prize || 0 }; + } + return { + ...prev, + prizePool, + } as TournamentResponseDto; + }); + }} errorMessage={ editTournamentState?.errors.prize0 && editTournamentState?.errors.prize0[0] @@ -113,9 +167,27 @@ const SettingsPage = () => { { + const prize = e.target.value ? Number(e.target.value) : undefined; + setTournamentData((prev) => { + const prizePool = [...(prev?.prizePool || [])]; + if (prizePool[1]) { + prizePool[1] = { ...prizePool[1], prize: prize || 0 }; + } else { + prizePool[1] = { type: 1, prize: prize || 0 }; + } + return { + ...prev, + prizePool, + } as TournamentResponseDto; + }); + }} errorMessage={ editTournamentState?.errors.prize1 && editTournamentState?.errors.prize1[0] @@ -124,9 +196,27 @@ const SettingsPage = () => { { + const prize = e.target.value ? Number(e.target.value) : undefined; + setTournamentData((prev) => { + const prizePool = [...(prev?.prizePool || [])]; + if (prizePool[2]) { + prizePool[2] = { ...prizePool[2], prize: prize || 0 }; + } else { + prizePool[2] = { type: 2, prize: prize || 0 }; + } + return { + ...prev, + prizePool, + } as TournamentResponseDto; + }); + }} errorMessage={ editTournamentState?.errors.prize2 && editTournamentState?.errors.prize2[0] @@ -137,14 +227,10 @@ const SettingsPage = () => {

    Rules

    { - setTournamentData((prevState) => { - return { - ...prevState, - rules: value, - } as TournamentResponseDto; - }); + setRules(value); + setRulesError(null); }} /> {rulesError && ( @@ -158,15 +244,23 @@ const SettingsPage = () => {
    { + if (!selectedImage) { + return toast.error("Please select an image", { + duration: 3000, + }); + } + formData.append("image", selectedImage); const data = await editTournamentImageAction(formData); if (!data.status) { - return toast.error("Failed to update tournament imag", { + return toast.error("Failed to update tournament image", { duration: 3000, }); } else { toast.success("Tournament image updated successfully", { duration: 3000, }); + setSelectedImage(null); + setImagePreview(null); } }} > @@ -177,17 +271,45 @@ const SettingsPage = () => { type={"hidden"} required /> - { - setImage(e.target.value); - }} - className={"mt-10"} - type={"file"} - required={true} - /> +
    + + + {imagePreview && ( +
    + Preview: +
    + +
    +
    + )} + {tournamentData && !imagePreview && ( +
    + Current image: +
    + +
    +
    + )} +
    diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/DeleteTeamModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/DeleteTeamModal.tsx new file mode 100644 index 0000000..e6b3192 --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/DeleteTeamModal.tsx @@ -0,0 +1,149 @@ +"use client"; + +import React, { useRef, useState } from "react"; +import { toast } from "sonner"; +import { useRouter } from "next/navigation"; +import { Button } from "@ui/atoms/Button/Button"; +import Modal from "@ui/organisms/Modal/Modal"; +import { deleteTeamAction } from "@/actions/admin/adminTeamActions"; + +interface IDeleteTeamModalProps { + tournamentAbb: string; + teamId: string; + teamName: string; +} + +export const DeleteTeamModal = ({ + tournamentAbb, + teamId, + teamName, +}: IDeleteTeamModalProps) => { + const modalRef = useRef(null); + const router = useRouter(); + const [isDeleting, setIsDeleting] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + + const handleDeleteClick = () => { + setShowConfirm(true); + }; + + const handleConfirmDelete = async () => { + setIsDeleting(true); + + const result = await deleteTeamAction(tournamentAbb, teamId); + setIsDeleting(false); + + if (!result.status) { + const errorMsg: string = result.errorMessage || "Failed to delete team"; + toast.error(errorMsg, { + duration: 3000, + }); + setShowConfirm(false); + return; + } + + toast.success("Team deleted successfully", { + duration: 3000, + }); + modalRef.current?.close(); + setShowConfirm(false); + router.refresh(); + }; + + const handleCancel = () => { + setShowConfirm(false); + modalRef.current?.close(); + }; + + return ( + <> + + + +

    + {showConfirm ? "Confirm Deletion" : "Delete Team"} +

    +
    + {!showConfirm ? ( + <> +

    + Are you sure you want to delete the team "{teamName}"? +

    +
    +

    Warning:

    +

    + This action will permanently delete all associated tournament data + including: +

    +
      +
    • All matches involving this team
    • +
    • All match results
    • +
    • All participants in this team
    • +
    • All related tournament data
    • +
    +

    + This action cannot be undone. +

    +
    +
    + + +
    + + ) : ( + <> +

    + Are you absolutely sure you want to delete the team{" "} + "{teamName}"? +

    +

    + This will permanently delete all associated data. This action cannot be + undone. +

    +
    + + +
    + + )} +
    +
    + + ); +}; + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/EditTeamModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/EditTeamModal.tsx new file mode 100644 index 0000000..d179104 --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/EditTeamModal.tsx @@ -0,0 +1,455 @@ +"use client"; + +import React, { useRef, useState, useMemo } from "react"; +import { toast } from "sonner"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import type { TeamResponseDto, ParticipantResponseDto } from "../../../../../../../client"; +import { RemoveParticipantModal } from "./RemoveParticipantModal"; +import { Button } from "@ui/atoms/Button/Button"; +import Modal from "@ui/organisms/Modal/Modal"; +import { Input } from "@ui/atoms/Forms/Input/Input"; +import { useTypeSafeFormState } from "@/hooks/useTypeSafeFormState"; +import { updateTeamSchema } from "@/formSchemas/updateTeamSchema"; +import { + updateTeamAction, + addParticipantAction, + removeParticipantAction, + changeCaptainAction, +} from "@/actions/admin/adminTeamActions"; + +interface IEditTeamModalProps { + tournamentAbb: string; + team: TeamResponseDto; +} + +export const EditTeamModal = ({ tournamentAbb, team }: IEditTeamModalProps) => { + const modalRef = useRef(null); + const formRef = useRef(null); + const router = useRouter(); + const [participants, setParticipants] = useState(team.participants); + const [captain, setCaptain] = useState(team.captain); + const [osuIdToAdd, setOsuIdToAdd] = useState(""); + const [isAddingParticipant, setIsAddingParticipant] = useState(false); + const [isRemovingParticipant, setIsRemovingParticipant] = useState(null); + const [isChangingCaptain, setIsChangingCaptain] = useState(null); + const [selectedLogo, setSelectedLogo] = useState(null); + const [logoPreview, setLogoPreview] = useState(null); + const [logoError, setLogoError] = useState(null); + + // Create refs for remove participant modals + const removeModalRefs = useMemo(() => { + const refs: Record> = {}; + participants.forEach((participant) => { + if (participant.id !== captain.id) { + refs[participant.id] = React.createRef(); + } + }); + return refs; + }, [participants, captain.id]); + + const validateLogo = (file: File): Promise => { + return new Promise((resolve) => { + // Check file size (max 2MB) + if (file.size > 2 * 1024 * 1024) { + resolve("Image size cannot exceed 2MB"); + return; + } + + // Check file extension + const allowedExtensions = [".jpg", ".jpeg", ".png"]; + const fileName = file.name.toLowerCase(); + const hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext)); + if (!hasValidExtension) { + resolve("Image must be in format: jpg, jpeg, or png"); + return; + } + + // Check image dimensions + const img = new window.Image(); + const objectUrl = URL.createObjectURL(file); + img.onload = () => { + URL.revokeObjectURL(objectUrl); + if (img.width > 50 || img.height > 50) { + resolve("Image dimensions cannot exceed 50x50 pixels"); + } else { + resolve(null); + } + }; + img.onerror = () => { + URL.revokeObjectURL(objectUrl); + resolve("Invalid image file"); + }; + img.src = objectUrl; + }); + }; + + const handleLogoChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) { + setSelectedLogo(null); + setLogoPreview(null); + setLogoError(null); + return; + } + + setLogoError(null); + + // Validate file + const error = await validateLogo(file); + if (error) { + setLogoError(error); + setSelectedLogo(null); + setLogoPreview(null); + e.target.value = ""; + return; + } + + setSelectedLogo(file); + + // Create preview + const reader = new FileReader(); + reader.onloadend = () => { + setLogoPreview(reader.result as string); + }; + reader.readAsDataURL(file); + }; + + const [state, formAction] = useTypeSafeFormState(updateTeamSchema, async (data) => { + // Create FormData from form data and logo file + const formDataToSend = new FormData(); + formDataToSend.append("tournamentAbbreviation", data.tournamentAbbreviation); + formDataToSend.append("teamId", data.teamId); + formDataToSend.append("name", data.name); + if (selectedLogo) { + formDataToSend.append("logo", selectedLogo); + } + + const updateTeamResponse = await updateTeamAction(formDataToSend); + if (!updateTeamResponse.status) { + const errorMsg: string = updateTeamResponse.errorMessage || "Failed to update team"; + return toast.error(errorMsg, { + duration: 3000, + }); + } + toast.success("Team updated successfully", { + duration: 3000, + }); + setSelectedLogo(null); + setLogoPreview(null); + setLogoError(null); + modalRef.current?.close(); + router.refresh(); + }); + + const handleAddParticipant = async () => { + if (!osuIdToAdd.trim()) { + toast.error("Please enter an osu! ID", { + duration: 3000, + }); + return; + } + + setIsAddingParticipant(true); + const result = await addParticipantAction(tournamentAbb, team.id, osuIdToAdd.trim()); + setIsAddingParticipant(false); + + if (!result.status) { + const errorMsg: string = result.errorMessage || "Failed to add participant"; + toast.error(errorMsg, { + duration: 3000, + }); + return; + } + + // Update local state with returned team data + if (result.team) { + setParticipants(result.team.participants || []); + if (result.team.captain) { + setCaptain(result.team.captain); + } + } + + toast.success("Participant added successfully", { + duration: 3000, + }); + setOsuIdToAdd(""); + router.refresh(); + }; + + const handleRemoveParticipant = async (participant: ParticipantResponseDto) => { + if (participant.id === captain.id) { + toast.error("Cannot remove team captain. Change captain first.", { + duration: 3000, + }); + return; + } + + setIsRemovingParticipant(participant.id); + const result = await removeParticipantAction( + tournamentAbb, + team.id, + participant.user.osuId.toString(), + ); + setIsRemovingParticipant(null); + + if (!result.status) { + const errorMsg: string = result.errorMessage || "Failed to remove participant"; + toast.error(errorMsg, { + duration: 3000, + }); + return; + } + + // Update local state with returned team data + if (result.team) { + setParticipants(result.team.participants || []); + if (result.team.captain) { + setCaptain(result.team.captain); + } + } + + toast.success("Participant removed successfully", { + duration: 3000, + }); + router.refresh(); + }; + + const handleChangeCaptain = async (participant: ParticipantResponseDto) => { + if (participant.id === captain.id) { + return; + } + + setIsChangingCaptain(participant.id); + const result = await changeCaptainAction( + tournamentAbb, + team.id, + participant.user.osuId.toString(), + ); + setIsChangingCaptain(null); + + if (!result.status) { + const errorMsg: string = result.errorMessage || "Failed to change captain"; + toast.error(errorMsg, { + duration: 3000, + }); + return; + } + + // Update local state with returned team data + if (result.team) { + setParticipants(result.team.participants || []); + if (result.team.captain) { + setCaptain(result.team.captain); + } + } + + toast.success("Captain changed successfully", { + duration: 3000, + }); + router.refresh(); + }; + + return ( + <> + + + +

    Edit Team: {team.name}

    + e.stopPropagation()}> +
    + + + + {/* Team Name */} + + + {/* Team Logo */} +
    + + + {logoError && ( +
    {logoError}
    + )} + {(logoPreview || (team.logoUrl && !selectedLogo && team.logoUrl !== "/aim_logo.svg")) && ( +
    + Preview: +
    +
    + +
    +
    +
    + )} +
    + + {/* Participants Management */} +
    Participants
    + + {/* Add Participant */} +
    + setOsuIdToAdd(e.target.value)} + placeholder="Enter osu! ID" + /> + +
    + + {/* Participants Table */} +
    +
    Referee Commentators StreamersActionActions
    (edit) +
    + +
    { + "use server"; + const result = await deleteMatchAction( + tournamentAbbreviation, + match.id, + ); + if (!result.status) { + throw new Error(result.errorMessage); + } + }} + > + +
    +
    +
    + + + + + + + + + + + {participants.map((participant) => ( + + + + + + + + ))} + +
    AvatarUsernameosu! IDStatusActions
    +
    +
    + {participant.user.username} +
    +
    +
    +
    {participant.user.username}
    +
    +
    {participant.user.osuId}
    +
    + {participant.id === captain.id ? ( + Captain + ) : ( + Member + )} + +
    + {participant.id !== captain.id ? ( + <> + + + + ) : ( + + Captain cannot be removed + + )} +
    +
    +
    + +
    + + +
    + + + + {/* Render remove participant modals outside the main modal to avoid nested forms */} + {participants.map((participant) => ( + participant.id !== captain.id && removeModalRefs[participant.id] && ( + handleRemoveParticipant(participant)} + isRemoving={isRemovingParticipant === participant.id} + /> + ) + ))} + + ); +}; + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/RemoveParticipantModal.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/RemoveParticipantModal.tsx new file mode 100644 index 0000000..2d68808 --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/RemoveParticipantModal.tsx @@ -0,0 +1,73 @@ +"use client"; + +import React from "react"; +import type { ParticipantResponseDto } from "../../../../../../../client"; +import { Button } from "@ui/atoms/Button/Button"; +import Modal from "@ui/organisms/Modal/Modal"; + +interface IRemoveParticipantModalProps { + modalRef: React.RefObject; + participant: ParticipantResponseDto; + onConfirm: () => Promise; + isRemoving?: boolean; +} + +export const RemoveParticipantModal = ({ + modalRef, + participant, + onConfirm, + isRemoving = false, +}: IRemoveParticipantModalProps) => { + + const handleConfirmRemove = async () => { + try { + await onConfirm(); + modalRef.current?.close(); + } catch (error) { + // Error handling is done in parent component + } + }; + + const handleCancel = () => { + modalRef.current?.close(); + }; + + return ( + +

    Remove Participant

    +
    +

    + Are you sure you want to remove "{participant.user.username}" from + the team? +

    +
    +

    Warning:

    +

    + This action will permanently remove the participant from the team and delete + their participation data. +

    +

    This action cannot be undone.

    +
    +
    + + +
    +
    +
    + ); +}; + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/TeamsTable.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/TeamsTable.tsx new file mode 100644 index 0000000..31f97fd --- /dev/null +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/TeamsTable.tsx @@ -0,0 +1,192 @@ +"use client"; + +import React, { useState, useTransition } from "react"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import type { TeamResponseDto, ParticipantResponseDto } from "../../../../../../../client"; +import { EditTeamModal } from "./EditTeamModal"; +import { DeleteTeamModal } from "./DeleteTeamModal"; +import { AvatarGroup } from "@ui/atoms/AvatarGroup/AvatarGroup"; +import { changeTeamStatusAction } from "@/actions/admin/adminTeamActions"; + +interface TeamsTableProps { + teams: TeamResponseDto[]; + tournamentAbbreviation: string; + canUpdateTeam: boolean; + canDeleteTeam: boolean; + canChangeTeamStatus: boolean; +} + +export const TeamsTable = ({ + teams, + tournamentAbbreviation, + canUpdateTeam, + canDeleteTeam, + canChangeTeamStatus, +}: TeamsTableProps) => { + const [isCompact, setIsCompact] = useState(false); + const [isPending, startTransition] = useTransition(); + const router = useRouter(); + + // Filter out captain from participants for compact view + const getNonCaptainParticipants = (team: TeamResponseDto): ParticipantResponseDto[] => { + return team.participants.filter( + (participant) => participant.id !== team.captain.id, + ); + }; + + const handleChangeTeamStatus = async (teamId: string, status: "ACCEPTED" | "REJECTED") => { + startTransition(async () => { + const result = await changeTeamStatusAction(tournamentAbbreviation, teamId, status); + if (result.status) { + toast.success(`Team status changed to ${status}`, { + duration: 3000, + }); + router.refresh(); + } else { + const errorMsg: string = result.errorMessage || "Failed to change team status"; + toast.error(errorMsg, { + duration: 3000, + }); + } + }); + }; + + return ( +
    +
    +

    Teams

    + +
    +
    + + + + + + + + + + + + {teams.map((team) => { + const nonCaptainParticipants = getNonCaptainParticipants(team); + return ( + + + + + + + + ); + })} + +
    Team nameCaptainRosterStatusActions
    +
    +
    +
    + Team logo +
    +
    + {team.name} +
    +
    +
    +
    +
    + {team.captain.user.username} +
    +
    + {team.captain.user.username} +
    +
    + {isCompact && nonCaptainParticipants.length > 0 ? ( + + ) : ( +
    + {team.participants.map((participant) => ( +
    +
    +
    + {participant.user.username} +
    +
    + {participant.user.username} +
    + ))} +
    + )} +
    {team.status} +
    + {canChangeTeamStatus && ( + <> + + + + )} + {canUpdateTeam && ( + + )} + {canDeleteTeam && ( + + )} +
    +
    +
    +
    + ); +}; + diff --git a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx index c547374..30eb420 100644 --- a/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx +++ b/src/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/teams/page.tsx @@ -1,8 +1,7 @@ import React from "react"; -import Image from "next/image"; import { cookies } from "next/headers"; -import { changeTeamStatus, client, deleteTeam, getTeams } from "../../../../../../../client"; -import { multipleRevalidatePaths } from "@/lib/multipleRevalidatePaths"; +import { client, getTeams, getTournamentStaffMember } from "../../../../../../../client"; +import { TeamsTable } from "./TeamsTable"; const TeamsPage = async ({ params: { tournamentAbbreviation }, @@ -29,192 +28,50 @@ const TeamsPage = async ({ if (error) { return
    Failed to fetch teams
    ; } + + const { data: getStaffForATournamentUser } = await getTournamentStaffMember({ + path: { + abbreviation: tournamentAbbreviation, + }, + }); + + // Check permissions + const canUpdateTeam = + getStaffForATournamentUser?.permissions?.some((permission) => { + return permission === "TEAM_UPDATE"; + }) || + getStaffForATournamentUser?.roles?.some((role) => + role.permissions.some((permission) => permission === "TEAM_UPDATE"), + ); + + const canDeleteTeam = + getStaffForATournamentUser?.permissions?.some((permission) => { + return permission === "TEAM_DELETE"; + }) || + getStaffForATournamentUser?.roles?.some((role) => + role.permissions.some((permission) => permission === "TEAM_DELETE"), + ); + + const canChangeTeamStatus = + getStaffForATournamentUser?.permissions?.some((permission) => { + return permission === "TEAM_STATUS_UPDATE"; + }) || + getStaffForATournamentUser?.roles?.some((role) => + role.permissions.some((permission) => permission === "TEAM_STATUS_UPDATE"), + ); + + if (!data) { + return
    Failed to fetch teams
    ; + } + return ( -
    -

    Teams

    -
    - - {/* head */} - - - - - - - - - - - {data?.map((team) => { - return ( - - - - - - - - ); - })} - -
    Team nameCaptainRosterStatusActions
    -
    -
    -
    - Team logo -
    -
    - {team.name} -
    -
    -
    -
    -
    - {team.captain.user.username} -
    -
    - {team.captain.user.username} -
    -
    - {team.participants.map((participant) => ( -
    -
    -
    -
    - {participant.user.username} -
    -
    - {participant.user.username} -
    -
    - ))} -
    {team.status} -
    { - "use server"; - const cookie = cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await changeTeamStatus({ - path: { - abbreviation: tournamentAbbreviation, - teamId: team.id, - }, - query: { - status: "ACCEPTED", - }, - }); - await multipleRevalidatePaths([ - "/", - `/dashboard/${tournamentAbbreviation}/teams`, - `/tournament/${tournamentAbbreviation}/teams/${team.id}`, - `/account/`, - ]); - }} - > - -
    -
    { - "use server"; - const cookie = cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await changeTeamStatus({ - path: { - abbreviation: tournamentAbbreviation, - teamId: team.id, - }, - query: { - status: "REJECTED", - }, - }); - await multipleRevalidatePaths([ - "/", - `/dashboard/${tournamentAbbreviation}/teams`, - `/tournament/${tournamentAbbreviation}/teams/${team.id}`, - `/account/`, - ]); - }} - > - -
    -
    { - "use server"; - const cookie = cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await deleteTeam({ - path: { - abbreviation: tournamentAbbreviation, - teamId: team.id, - }, - }); - await multipleRevalidatePaths([ - "/", - `/dashboard/${tournamentAbbreviation}/teams`, - `/tournament/${tournamentAbbreviation}/teams/${team.id}`, - `/account/`, - ]); - }} - > - -
    -
    -
    -
    + ); }; diff --git a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx index 0bc0937..05cceab 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/mappool/[stageType]/page.tsx @@ -4,10 +4,12 @@ import { type BeatmapModificationResponseDto, client, getMappoolByStage, + getStages, type StageResponseDto, } from "../../../../../../../client"; import { stageTypeEnumToString } from "@/lib/helpers"; -import { MappoolCard } from "@ui/molecules/Cards/MappoolCard"; +import { BeatmapListItem } from "@ui/molecules/BeatmapListItem/BeatmapListItem"; +import { StageNavigation } from "@ui/organisms/StageNavigation/StageNavigation"; import Section from "@ui/atoms/Section/Section"; import { Button } from "@ui/atoms/Button/Button"; @@ -33,6 +35,7 @@ const SingleTournamentMappool = async ({ Cookie: `token=${cookie}`, }, }); + let getMappoolByStageData; try { const { data: getMappoolByStageData1 } = await getMappoolByStage({ @@ -46,57 +49,88 @@ const SingleTournamentMappool = async ({ console.error(error); } + const { data: getStagesData } = await getStages({ + path: { + abbreviation: params.tournamentId, + }, + }); + + // Collect all beatmaps from all modifications, sorted + const allBeatmaps = getMappoolByStageData?.beatmapsModifications + .flatMap((bm) => + bm?.beatmaps + ?.filter((map) => { + if (!searchParams.modification) { + return true; + } + return map.modification === searchParams.modification; + }) + ?.map((map) => ({ + ...map, + modification: bm.modification, + })) || [], + ) + .sort((a, b) => { + // Sort by modification first, then by position + if (a.modification !== b.modification) { + const modOrder = ["NM", "HD", "HR", "DT", "FM", "TB"]; + const aIndex = modOrder.indexOf(a.modification || ""); + const bIndex = modOrder.indexOf(b.modification || ""); + return aIndex - bIndex; + } + return a.position - b.position; + }) || []; + return ( <> + {getStagesData && ( + + )}
    -
    -
    +
    +

    Mappool

    {stageTypeEnumToString(params.stageType)}

    + {getMappoolByStageData?.downloadUrl && ( + + )}
    - {getMappoolByStageData?.downloadUrl && ( - - )} - - {getMappoolByStageData?.beatmapsModifications.map((bm) => ( -
    - {bm?.beatmaps - ?.filter((map) => { - if (!searchParams.modification) { - return true; - } - return map.modification === searchParams.modification; - }) - ?.sort((a, b) => a.position - b.position) - ?.map((map) => ( - - ))} -
    - ))} +
    + {allBeatmaps.map((map) => ( + + ))} +
    ); diff --git a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx index 7c7c6e3..fd33d1d 100644 --- a/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx +++ b/src/app/(tournament)/tournament/[tournamentId]/schedule/page.tsx @@ -4,6 +4,8 @@ import { format } from "date-fns"; import Link from "next/link"; import { client, getMatches } from "../../../../../../client"; import Section from "@ui/atoms/Section/Section"; +import { getUser } from "@/actions/public/getUserAction"; +import { RescheduleMatchModal } from "@/app/(main-page)/(withAuth)/dashboard/[tournamentAbbreviation]/matches/RescheduleMatchModal"; const SingleTournamentSchedule = async ({ params, @@ -22,6 +24,7 @@ const SingleTournamentSchedule = async ({ Cookie: `token=${cookie}`, }, }); + const userData = await getUser(); const { data } = await getMatches({ path: { abbreviation: params.tournamentId, @@ -44,6 +47,7 @@ const SingleTournamentSchedule = async ({
    Referee Commentators StreamersAction
    + {(match.teamRed?.captain?.user?.id === userData?.id || + match.teamBlue?.captain?.user?.id === userData?.id) && ( + + )} +
    {participant.user.id !== userData?.id && ( // if not captain -
    { - "use server"; - const cookie = cookies().get("JWT")?.value; - // configure internal service client - client.setConfig({ - // set default base url for requests - baseUrl: process.env.NEXT_PUBLIC_API_URL, - // set default headers for requests - headers: { - Cookie: `token=${cookie}`, - }, - }); - await deleteParticipantFromTeam({ - path: { - teamId: getTeam?.id || "", - participantId: "" + participant.id, - abbreviation: tournamentId, - }, - }); - - await multipleRevalidatePaths([ - `/tournament/${tournamentId}/teams`, - `/tournament/${tournamentId}`, - "/", - ]); - }} - > - -
    + )}