From 69aeb7889ac136a8e4fbe7de1330298e30345479 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:59:12 +0400 Subject: feat/frontend: switch to vite, update node to v22 (#281) --- frontend/src/App.tsx | 32 ++-- frontend/src/api/Api.ts | 18 +- frontend/src/api/Auth.ts | 2 +- frontend/src/api/Games.ts | 2 +- frontend/src/api/Maps.ts | 4 +- frontend/src/api/Rankings.ts | 4 +- frontend/src/api/User.ts | 4 +- frontend/src/components/ConfirmDialog.tsx | 32 ++-- frontend/src/components/Discussions.tsx | 16 +- frontend/src/components/GameCategory.tsx | 22 +-- frontend/src/components/GameEntry.tsx | 6 +- frontend/src/components/Leaderboards.tsx | 144 +++++++-------- frontend/src/components/Login.tsx | 10 +- frontend/src/components/MapEntry.tsx | 10 +- frontend/src/components/MessageDialog.tsx | 30 ++-- frontend/src/components/MessageDialogLoad.tsx | 30 ++-- frontend/src/components/ModMenu.tsx | 18 +- frontend/src/components/RankingEntry.tsx | 58 +++--- frontend/src/components/Sidebar.tsx | 42 ++--- frontend/src/components/Summary.tsx | 12 +- frontend/src/components/UploadRunDialog.tsx | 22 +-- frontend/src/hooks/UseConfirm.tsx | 56 +++--- frontend/src/hooks/UseMessage.tsx | 52 +++--- frontend/src/hooks/UseMessageLoad.tsx | 48 ++--- frontend/src/images/Images.tsx | 40 ++--- frontend/src/index.tsx | 8 +- frontend/src/pages/About.tsx | 60 +++---- frontend/src/pages/Games.tsx | 64 +++---- frontend/src/pages/Homepage.tsx | 32 ++-- frontend/src/pages/Maps.tsx | 22 +-- frontend/src/pages/Profile.tsx | 46 ++--- frontend/src/pages/Rankings.tsx | 248 +++++++++++++------------- frontend/src/pages/Rules.tsx | 62 +++---- frontend/src/pages/User.tsx | 44 ++--- frontend/src/react-app-env.d.ts | 2 - frontend/src/types/Content.ts | 4 - frontend/src/types/Game.ts | 2 +- frontend/src/types/Map.ts | 6 +- frontend/src/types/MapNames.ts | 220 +++++++++++------------ frontend/src/utils/Jwt.ts | 20 +-- frontend/src/utils/Time.ts | 30 ++-- 41 files changed, 789 insertions(+), 795 deletions(-) delete mode 100644 frontend/src/react-app-env.d.ts (limited to 'frontend/src') diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bdd3adc..e7f225c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,24 +1,24 @@ -import React from 'react'; +import React from "react"; import { Routes, Route } from "react-router-dom"; import { Helmet } from "react-helmet"; -import { UserProfile } from '@customTypes/Profile'; -import Sidebar from './components/Sidebar'; +import { UserProfile } from "@customTypes/Profile"; +import Sidebar from "./components/Sidebar"; import "./App.css"; -import Profile from '@pages/Profile'; -import Games from '@pages/Games'; -import Maps from '@pages/Maps'; -import User from '@pages/User'; -import Homepage from '@pages/Homepage'; -import UploadRunDialog from './components/UploadRunDialog'; -import Rules from '@pages/Rules'; -import About from '@pages/About'; -import { Game } from '@customTypes/Game'; -import { API } from './api/Api'; -import Maplist from '@pages/Maplist'; -import Rankings from '@pages/Rankings'; -import { get_user_id_from_token, get_user_mod_from_token } from './utils/Jwt'; +import Profile from "@pages/Profile"; +import Games from "@pages/Games"; +import Maps from "@pages/Maps"; +import User from "@pages/User"; +import Homepage from "@pages/Homepage"; +import UploadRunDialog from "./components/UploadRunDialog"; +import Rules from "@pages/Rules"; +import About from "@pages/About"; +import { Game } from "@customTypes/Game"; +import { API } from "./api/Api"; +import Maplist from "@pages/Maplist"; +import Rankings from "@pages/Rankings"; +import { get_user_id_from_token, get_user_mod_from_token } from "./utils/Jwt"; const App: React.FC = () => { const [token, setToken] = React.useState(undefined); diff --git a/frontend/src/api/Api.ts b/frontend/src/api/Api.ts index c84714d..df787e3 100644 --- a/frontend/src/api/Api.ts +++ b/frontend/src/api/Api.ts @@ -1,11 +1,11 @@ -import { MapDiscussionCommentContent, MapDiscussionContent, ModMenuContent } from '@customTypes/Content'; -import { delete_token, get_token } from '@api/Auth'; -import { get_user, get_profile, post_profile } from '@api/User'; -import { get_games, get_chapters, get_games_chapters, get_game_maps, get_search } from '@api/Games'; -import { get_official_rankings, get_unofficial_rankings } from '@api/Rankings'; -import { get_map_summary, get_map_leaderboard, get_map_discussions, get_map_discussion, post_map_discussion, post_map_discussion_comment, delete_map_discussion, post_record, delete_map_record } from '@api/Maps'; -import { delete_map_summary, post_map_summary, put_map_image, put_map_summary } from '@api/Mod'; -import { UploadRunContent } from '@customTypes/Content'; +import { MapDiscussionContent, ModMenuContent } from "@customTypes/Content"; +import { delete_token, get_token } from "@api/Auth"; +import { get_user, get_profile, post_profile } from "@api/User"; +import { get_games, get_chapters, get_games_chapters, get_game_maps, get_search } from "@api/Games"; +import { get_official_rankings, get_unofficial_rankings } from "@api/Rankings"; +import { get_map_summary, get_map_leaderboard, get_map_discussions, get_map_discussion, post_map_discussion, post_map_discussion_comment, delete_map_discussion, post_record, delete_map_record } from "@api/Maps"; +import { delete_map_summary, post_map_summary, put_map_image, put_map_summary } from "@api/Mod"; +import { UploadRunContent } from "@customTypes/Content"; // add new api call function entries here // example usage: API.get_games(); @@ -49,7 +49,7 @@ export const API = { delete_map_summary: (token: string, map_id: string, route_id: number) => delete_map_summary(token, map_id, route_id), }; -const BASE_API_URL: string = "/api/v1/" +const BASE_API_URL: string = "https://lp.portal2.sr/api/v1/" export function url(path: string): string { return BASE_API_URL + path; diff --git a/frontend/src/api/Auth.ts b/frontend/src/api/Auth.ts index 875c7e5..56e0f6a 100644 --- a/frontend/src/api/Auth.ts +++ b/frontend/src/api/Auth.ts @@ -2,7 +2,7 @@ import axios from "axios"; import { url } from "@api/Api"; export const get_token = async (): Promise => { - const response = await axios.get(url(`token`)) + const response = await axios.get(url("token")) if (!response.data.success) { return undefined; } diff --git a/frontend/src/api/Games.ts b/frontend/src/api/Games.ts index 72bb4b3..98c65e7 100644 --- a/frontend/src/api/Games.ts +++ b/frontend/src/api/Games.ts @@ -6,7 +6,7 @@ import { Map } from "@customTypes/Map"; import { Search } from "@customTypes/Search"; export const get_games = async (): Promise => { - const response = await axios.get(url(`games`)) + const response = await axios.get(url("games")) return response.data.data; }; diff --git a/frontend/src/api/Maps.ts b/frontend/src/api/Maps.ts index aa967ce..8d29f84 100644 --- a/frontend/src/api/Maps.ts +++ b/frontend/src/api/Maps.ts @@ -17,9 +17,9 @@ export const get_map_leaderboard = async (map_id: string, page: string): Promise // map the kind of leaderboard data.records = data.records.map((record: any) => { if (record.host && record.partner) { - return { ...record, kind: 'multiplayer' }; + return { ...record, kind: "multiplayer" }; } else { - return { ...record, kind: 'singleplayer' }; + return { ...record, kind: "singleplayer" }; } }); return data; diff --git a/frontend/src/api/Rankings.ts b/frontend/src/api/Rankings.ts index b8d9bec..a5f4c88 100644 --- a/frontend/src/api/Rankings.ts +++ b/frontend/src/api/Rankings.ts @@ -3,11 +3,11 @@ import { url } from "@api/Api"; import { Ranking, SteamRanking } from "@customTypes/Ranking"; export const get_official_rankings = async (): Promise => { - const response = await axios.get(url(`rankings/lphub`)); + const response = await axios.get(url("rankings/lphub")); return response.data.data; }; export const get_unofficial_rankings = async (): Promise => { - const response = await axios.get(url(`rankings/steam`)); + const response = await axios.get(url("rankings/steam")); return response.data.data; }; diff --git a/frontend/src/api/User.ts b/frontend/src/api/User.ts index 88da0f2..995902c 100644 --- a/frontend/src/api/User.ts +++ b/frontend/src/api/User.ts @@ -8,7 +8,7 @@ export const get_user = async (user_id: string): Promise => { }; export const get_profile = async (token: string): Promise => { - const response = await axios.get(url(`profile`), { + const response = await axios.get(url("profile"), { headers: { "Authorization": token, } @@ -17,7 +17,7 @@ export const get_profile = async (token: string): Promise => { }; export const post_profile = async (token: string) => { - const _ = await axios.post(url(`profile`), {}, { + await axios.post(url("profile"), {}, { headers: { "Authorization": token, } diff --git a/frontend/src/components/ConfirmDialog.tsx b/frontend/src/components/ConfirmDialog.tsx index 44a653b..d8784d2 100644 --- a/frontend/src/components/ConfirmDialog.tsx +++ b/frontend/src/components/ConfirmDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import "@css/Dialog.css" @@ -10,22 +10,22 @@ interface ConfirmDialogProps { }; const ConfirmDialog: React.FC = ({ title, subtitle, onConfirm, onCancel }) => { - return ( -
-
-
- {title} -
-
- {subtitle} -
-
- - -
-
+ return ( +
+
+
+ {title}
- ) +
+ {subtitle} +
+
+ + +
+
+
+ ) }; export default ConfirmDialog; diff --git a/frontend/src/components/Discussions.tsx b/frontend/src/components/Discussions.tsx index 17ae586..0c37355 100644 --- a/frontend/src/components/Discussions.tsx +++ b/frontend/src/components/Discussions.tsx @@ -1,12 +1,12 @@ -import React from 'react'; +import React from "react"; -import { MapDiscussion, MapDiscussions, MapDiscussionsDetail } from '@customTypes/Map'; -import { MapDiscussionCommentContent, MapDiscussionContent } from '@customTypes/Content'; -import { time_ago } from '@utils/Time'; -import { API } from '@api/Api'; +import { MapDiscussion, MapDiscussions, MapDiscussionsDetail } from "@customTypes/Map"; +import { MapDiscussionContent } from "@customTypes/Content"; +import { time_ago } from "@utils/Time"; +import { API } from "@api/Api"; import "@css/Maps.css" -import { Link } from 'react-router-dom'; -import useConfirm from '@hooks/UseConfirm'; +import { Link } from "react-router-dom"; +import useConfirm from "@hooks/UseConfirm"; interface DiscussionsProps { token?: string @@ -141,7 +141,7 @@ const Discussions: React.FC = ({ token, data, isModerator, map data ? (<> {data.discussions.filter(f => f.title.includes(discussionSearch)).sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) - .map((e, i) => ( + .map((e) => (
- {data.pagination.current_page}/{data.pagination.total_pages} - +
+ Place + + {data.map.is_coop ? ( +
+ Blue + Orange +
+ ) : ( + Runner + )} + + Portals + Time + Date +
+
+ + + {data.pagination.current_page}/{data.pagination.total_pages} + +
-
-
-
- {data.records.map((r, index) => ( - - {r.placement} - - {r.kind === "multiplayer" ? ( -
+
+
+ {data.records.map((r, index) => ( + + {r.placement} + + {r.kind === "multiplayer" ? ( +
  {r.host.user_name}   {r.partner.user_name} -
- ) : r.kind === "singleplayer" && ( -
+
+ ) : r.kind === "singleplayer" && ( +
  {r.user.user_name} -
- )} - - {r.score_count} - - {ticks_to_time(r.score_time)} - {time_ago(new Date(r.record_date.replace("T", " ").replace("Z", "")))} - - {r.kind === "multiplayer" ? ( - - - - - - ) : r.kind === "singleplayer" && ( - - - - - - )} -
- ))} -
- -
+
+ )} + + {r.score_count} + + {ticks_to_time(r.score_time)} + {time_ago(new Date(r.record_date.replace("T", " ").replace("Z", "")))} + + {r.kind === "multiplayer" ? ( + + + + + + ) : r.kind === "singleplayer" && ( + + + + + + )} + + ))} +
+ + ); }; diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx index f1628b2..89a37e1 100644 --- a/frontend/src/components/Login.tsx +++ b/frontend/src/components/Login.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import React from "react"; +import { Link, useNavigate } from "react-router-dom"; -import { ExitIcon, UserIcon, LoginIcon } from '@images/Images'; -import { UserProfile } from '@customTypes/Profile'; -import { API } from '@api/Api'; +import { ExitIcon, UserIcon, LoginIcon } from "@images/Images"; +import { UserProfile } from "@customTypes/Profile"; +import { API } from "@api/Api"; import "@css/Login.css"; interface LoginProps { diff --git a/frontend/src/components/MapEntry.tsx b/frontend/src/components/MapEntry.tsx index 0f494ad..88137d8 100644 --- a/frontend/src/components/MapEntry.tsx +++ b/frontend/src/components/MapEntry.tsx @@ -1,12 +1,12 @@ -import React from 'react'; +import React from "react"; import { Link } from "react-router-dom"; const MapEntry: React.FC = () => { - return ( -
+ return ( +
-
- ) +
+ ) } export default MapEntry; diff --git a/frontend/src/components/MessageDialog.tsx b/frontend/src/components/MessageDialog.tsx index 5c85189..ae95f8d 100644 --- a/frontend/src/components/MessageDialog.tsx +++ b/frontend/src/components/MessageDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import "@css/Dialog.css" @@ -9,21 +9,21 @@ interface MessageDialogProps { }; const MessageDialog: React.FC = ({ title, subtitle, onClose }) => { - return ( -
-
-
- {title} -
-
- {subtitle} -
-
- -
-
+ return ( +
+
+
+ {title}
- ) +
+ {subtitle} +
+
+ +
+
+
+ ) } export default MessageDialog; diff --git a/frontend/src/components/MessageDialogLoad.tsx b/frontend/src/components/MessageDialogLoad.tsx index 966e064..000a2ab 100644 --- a/frontend/src/components/MessageDialogLoad.tsx +++ b/frontend/src/components/MessageDialogLoad.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import "@css/Dialog.css" @@ -8,22 +8,22 @@ interface MessageDialogLoadProps { }; const MessageDialogLoad: React.FC = ({ title, onClose }) => { - return ( -
-
-
- {title} -
-
-
+ return ( +
+
+
+ {title} +
+
+
-
-
-
-
-
+
+
+
- ) +
+
+ ) } export default MessageDialogLoad; diff --git a/frontend/src/components/ModMenu.tsx b/frontend/src/components/ModMenu.tsx index 925b8a8..19ce0ce 100644 --- a/frontend/src/components/ModMenu.tsx +++ b/frontend/src/components/ModMenu.tsx @@ -1,12 +1,12 @@ -import React from 'react'; -import ReactMarkdown from 'react-markdown'; -import { useNavigate } from 'react-router-dom'; +import React from "react"; +import ReactMarkdown from "react-markdown"; +import { useNavigate } from "react-router-dom"; -import { MapSummary } from '@customTypes/Map'; -import { ModMenuContent } from '@customTypes/Content'; -import { API } from '@api/Api'; +import { MapSummary } from "@customTypes/Map"; +import { ModMenuContent } from "@customTypes/Content"; +import { API } from "@api/Api"; import "@css/ModMenu.css" -import useConfirm from '@hooks/UseConfirm'; +import useConfirm from "@hooks/UseConfirm"; interface ModMenuProps { token?: string; @@ -55,10 +55,10 @@ const ModMenu: React.FC = ({ token, data, selectedRun, mapID }) => width *= 320 / height; height = 320; } - const canvas = document.createElement('canvas'); + const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; - canvas.getContext('2d')!.drawImage(img, 0, 0, width, height); + canvas.getContext("2d")!.drawImage(img, 0, 0, width, height); resolve(canvas.toDataURL(file.type, 0.6)); }; } diff --git a/frontend/src/components/RankingEntry.tsx b/frontend/src/components/RankingEntry.tsx index b899965..9ad9e1c 100644 --- a/frontend/src/components/RankingEntry.tsx +++ b/frontend/src/components/RankingEntry.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React from "react"; import { Link } from "react-router-dom"; -import { RankingType, SteamRanking, SteamRankingType } from '@customTypes/Ranking'; +import { RankingType, SteamRankingType } from "@customTypes/Ranking"; enum RankingCategories { rankings_overall, @@ -14,33 +14,33 @@ interface RankingEntryProps { }; const RankingEntry: React.FC = (prop) => { - if ("placement" in prop.curRankingData) { - return ( -
- {prop.curRankingData.placement} -
- - - {prop.curRankingData.user.user_name} - -
- {prop.curRankingData.total_score} -
- ) - } else { - return ( -
- {prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_rank : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_rank : prop.curRankingData.overall_rank} -
- - - {prop.curRankingData.user_name} - -
- {prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_score : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_score : prop.curRankingData.overall_score} -
- ) - } + if ("placement" in prop.curRankingData) { + return ( +
+ {prop.curRankingData.placement} +
+ + + {prop.curRankingData.user.user_name} + +
+ {prop.curRankingData.total_score} +
+ ) + } else { + return ( +
+ {prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_rank : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_rank : prop.curRankingData.overall_rank} +
+ + + {prop.curRankingData.user_name} + +
+ {prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_score : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_score : prop.curRankingData.overall_score} +
+ ) + } } export default RankingEntry; diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index f972c6f..eef5bca 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import { Link, useLocation } from 'react-router-dom'; - -import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; -import Login from '@components/Login'; -import { UserProfile } from '@customTypes/Profile'; -import { Search } from '@customTypes/Search'; -import { API } from '@api/Api'; +import React from "react"; +import { Link, useLocation } from "react-router-dom"; + +import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from "@images/Images"; +import Login from "@components/Login"; +import { UserProfile } from "@customTypes/Profile"; +import { Search } from "@customTypes/Search"; +import { API } from "@api/Api"; import "@css/Sidebar.css"; interface SidebarProps { @@ -39,7 +39,7 @@ const Sidebar: React.FC = ({ setToken, profile, setProfile, onUplo }; const _handle_sidebar_hide = () => { - var btn = document.querySelectorAll("button.sidebar-button") as NodeListOf + const btn = document.querySelectorAll("button.sidebar-button") as NodeListOf const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf const side = document.querySelector("#sidebar-list") as HTMLElement; const searchbar = document.querySelector("#searchbar") as HTMLInputElement; @@ -140,13 +140,13 @@ const Sidebar: React.FC = ({ setToken, profile, setProfile, onUplo
- -