aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/pages')
-rw-r--r--frontend/src/pages/About.tsx60
-rw-r--r--frontend/src/pages/Games.tsx64
-rw-r--r--frontend/src/pages/Homepage.tsx32
-rw-r--r--frontend/src/pages/Maps.tsx22
-rw-r--r--frontend/src/pages/Profile.tsx46
-rw-r--r--frontend/src/pages/Rankings.tsx248
-rw-r--r--frontend/src/pages/Rules.tsx62
-rw-r--r--frontend/src/pages/User.tsx44
8 files changed, 289 insertions, 289 deletions
diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx
index a8b7826..e4abdf4 100644
--- a/frontend/src/pages/About.tsx
+++ b/frontend/src/pages/About.tsx
@@ -1,40 +1,40 @@
1import React from 'react'; 1import React from "react";
2import ReactMarkdown from 'react-markdown'; 2import ReactMarkdown from "react-markdown";
3import { Helmet } from 'react-helmet'; 3import { Helmet } from "react-helmet";
4 4
5import '@css/About.css'; 5import "@css/About.css";
6 6
7const About: React.FC = () => { 7const About: React.FC = () => {
8 8
9 const [aboutText, setAboutText] = React.useState<string>(""); 9 const [aboutText, setAboutText] = React.useState<string>("");
10 10
11 React.useEffect(() => { 11 React.useEffect(() => {
12 const fetchReadme = async () => { 12 const fetchReadme = async () => {
13 try { 13 try {
14 const response = await fetch( 14 const response = await fetch(
15 'https://raw.githubusercontent.com/pektezol/lphub/main/README.md' 15 "https://raw.githubusercontent.com/pektezol/lphub/main/README.md"
16 ); 16 );
17 if (!response.ok) { 17 if (!response.ok) {
18 throw new Error('Failed to fetch README'); 18 throw new Error("Failed to fetch README");
19 } 19 }
20 const readmeText = await response.text(); 20 const readmeText = await response.text();
21 setAboutText(readmeText); 21 setAboutText(readmeText);
22 } catch (error) { 22 } catch (error) {
23 console.error('Error fetching README:', error); 23 console.error("Error fetching README:", error);
24 } 24 }
25 }; 25 };
26 fetchReadme(); 26 fetchReadme();
27 }, []); 27 }, []);
28 28
29 29
30 return ( 30 return (
31 <div id="about"> 31 <div id="about">
32 <Helmet> 32 <Helmet>
33 <title>LPHUB | About</title> 33 <title>LPHUB | About</title>
34 </Helmet> 34 </Helmet>
35 <ReactMarkdown>{aboutText}</ReactMarkdown> 35 <ReactMarkdown>{aboutText}</ReactMarkdown>
36 </div> 36 </div>
37 ); 37 );
38}; 38};
39 39
40export default About; 40export default About;
diff --git a/frontend/src/pages/Games.tsx b/frontend/src/pages/Games.tsx
index 15cc891..909ea20 100644
--- a/frontend/src/pages/Games.tsx
+++ b/frontend/src/pages/Games.tsx
@@ -1,46 +1,46 @@
1import React from 'react'; 1import React from "react";
2import { Helmet } from 'react-helmet'; 2import { Helmet } from "react-helmet";
3 3
4import GameEntry from '@components/GameEntry'; 4import GameEntry from "@components/GameEntry";
5import { Game } from '@customTypes/Game'; 5import { Game } from "@customTypes/Game";
6import "@css/Maps.css" 6import "@css/Maps.css"
7 7
8interface GamesProps { 8interface GamesProps {
9 games: Game[]; 9 games: Game[];
10} 10}
11 11
12const Games: React.FC<GamesProps> = ({ games }) => { 12const Games: React.FC<GamesProps> = ({ games }) => {
13 13
14 const _page_load = () => { 14 const _page_load = () => {
15 const loaders = document.querySelectorAll(".loader"); 15 const loaders = document.querySelectorAll(".loader");
16 loaders.forEach((loader) => { 16 loaders.forEach((loader) => {
17 (loader as HTMLElement).style.display = "none"; 17 (loader as HTMLElement).style.display = "none";
18 }); 18 });
19 } 19 }
20 20
21 React.useEffect(() => { 21 React.useEffect(() => {
22 document.querySelectorAll(".games-page-item-body").forEach((game, index) => { 22 document.querySelectorAll(".games-page-item-body").forEach((game, index) => {
23 game.innerHTML = ""; 23 game.innerHTML = "";
24 }); 24 });
25 _page_load(); 25 _page_load();
26 }, []); 26 }, []);
27 27
28 return ( 28 return (
29 <div className='games-page'> 29 <div className='games-page'>
30 <Helmet> 30 <Helmet>
31 <title>LPHUB | Games</title> 31 <title>LPHUB | Games</title>
32 </Helmet> 32 </Helmet>
33 <section> 33 <section>
34 <div className='games-page-content'> 34 <div className='games-page-content'>
35 <div className='games-page-item-content'> 35 <div className='games-page-item-content'>
36 {games.map((game, index) => ( 36 {games.map((game, index) => (
37 <GameEntry game={game} key={index} /> 37 <GameEntry game={game} key={index} />
38 ))} 38 ))}
39 </div> 39 </div>
40 </div>
41 </section>
42 </div> 40 </div>
43 ); 41 </section>
42 </div>
43 );
44}; 44};
45 45
46export default Games; 46export default Games;
diff --git a/frontend/src/pages/Homepage.tsx b/frontend/src/pages/Homepage.tsx
index 4f46af5..3f30d9a 100644
--- a/frontend/src/pages/Homepage.tsx
+++ b/frontend/src/pages/Homepage.tsx
@@ -1,22 +1,22 @@
1import React from 'react'; 1import React from "react";
2import { Helmet } from 'react-helmet'; 2import { Helmet } from "react-helmet";
3 3
4const Homepage: React.FC = () => { 4const Homepage: React.FC = () => {
5 5
6 return ( 6 return (
7 <main> 7 <main>
8 <Helmet> 8 <Helmet>
9 <title>LPHUB | Homepage</title> 9 <title>LPHUB | Homepage</title>
10 </Helmet> 10 </Helmet>
11 <section> 11 <section>
12 <p /> 12 <p />
13 <h1>Welcome to Least Portals Hub!</h1> 13 <h1>Welcome to Least Portals Hub!</h1>
14 <p>At the moment, LPHUB is in beta state. This means that the site has only the core functionalities enabled for providing both collaborative information and competitive leaderboards.</p> 14 <p>At the moment, LPHUB is in beta state. This means that the site has only the core functionalities enabled for providing both collaborative information and competitive leaderboards.</p>
15 <p>The website should feel intuitive to navigate around. For any type of feedback, reach us at LPHUB Discord server.</p> 15 <p>The website should feel intuitive to navigate around. For any type of feedback, reach us at LPHUB Discord server.</p>
16 <p>By using LPHUB, you agree that you have read the 'Leaderboard Rules' and the 'About LPHUB' pages.</p> 16 <p>By using LPHUB, you agree that you have read the 'Leaderboard Rules' and the 'About LPHUB' pages.</p>
17 </section> 17 </section>
18 </main> 18 </main>
19 ); 19 );
20}; 20};
21 21
22export default Homepage; 22export default Homepage;
diff --git a/frontend/src/pages/Maps.tsx b/frontend/src/pages/Maps.tsx
index fb13563..8bb5b32 100644
--- a/frontend/src/pages/Maps.tsx
+++ b/frontend/src/pages/Maps.tsx
@@ -1,14 +1,14 @@
1import React from 'react'; 1import React from "react";
2import { Link, useLocation } from 'react-router-dom'; 2import { Link, useLocation } from "react-router-dom";
3import { Helmet } from 'react-helmet'; 3import { Helmet } from "react-helmet";
4 4
5import { PortalIcon, FlagIcon, ChatIcon } from '@images/Images'; 5import { PortalIcon, FlagIcon, ChatIcon } from "@images/Images";
6import Summary from '@components/Summary'; 6import Summary from "@components/Summary";
7import Leaderboards from '@components/Leaderboards'; 7import Leaderboards from "@components/Leaderboards";
8import Discussions from '@components/Discussions'; 8import Discussions from "@components/Discussions";
9import ModMenu from '@components/ModMenu'; 9import ModMenu from "@components/ModMenu";
10import { MapDiscussions, MapLeaderboard, MapSummary } from '@customTypes/Map'; 10import { MapDiscussions, MapLeaderboard, MapSummary } from "@customTypes/Map";
11import { API } from '@api/Api'; 11import { API } from "@api/Api";
12import "@css/Maps.css"; 12import "@css/Maps.css";
13 13
14interface MapProps { 14interface MapProps {
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 48233bf..e7b8325 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -1,16 +1,16 @@
1import React from 'react'; 1import React from "react";
2import { Link, useNavigate } from 'react-router-dom'; 2import { Link, useNavigate } from "react-router-dom";
3import { Helmet } from 'react-helmet'; 3import { Helmet } from "react-helmet";
4 4
5import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon, DeleteIcon } from '@images/Images'; 5import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon, DeleteIcon } from "@images/Images";
6import { UserProfile } from '@customTypes/Profile'; 6import { UserProfile } from "@customTypes/Profile";
7import { Game, GameChapters } from '@customTypes/Game'; 7import { Game, GameChapters } from "@customTypes/Game";
8import { Map } from '@customTypes/Map'; 8import { Map } from "@customTypes/Map";
9import { ticks_to_time } from '@utils/Time'; 9import { ticks_to_time } from "@utils/Time";
10import "@css/Profile.css"; 10import "@css/Profile.css";
11import { API } from '@api/Api'; 11import { API } from "@api/Api";
12import useConfirm from '@hooks/UseConfirm'; 12import useConfirm from "@hooks/UseConfirm";
13import useMessage from '@hooks/UseMessage'; 13import useMessage from "@hooks/UseMessage";
14import useMessageLoad from "@hooks/UseMessageLoad"; 14import useMessageLoad from "@hooks/UseMessageLoad";
15 15
16interface ProfileProps { 16interface ProfileProps {
@@ -193,9 +193,9 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
193 193
194 <select id='select-game' 194 <select id='select-game'
195 onChange={() => { 195 onChange={() => {
196 setGame((document.querySelector('#select-game') as HTMLInputElement).value); 196 setGame((document.querySelector("#select-game") as HTMLInputElement).value);
197 setChapter("0"); 197 setChapter("0");
198 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement; 198 const chapterSelect = document.querySelector("#select-chapter") as HTMLSelectElement;
199 if (chapterSelect) { 199 if (chapterSelect) {
200 chapterSelect.value = "0"; 200 chapterSelect.value = "0";
201 } 201 }
@@ -213,7 +213,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
213 : chapterData === null ? <select></select> : 213 : chapterData === null ? <select></select> :
214 214
215 <select id='select-chapter' 215 <select id='select-chapter'
216 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}> 216 onChange={() => setChapter((document.querySelector("#select-chapter") as HTMLInputElement).value)}>
217 <option value="0" key="0">All Chapters</option> 217 <option value="0" key="0">All Chapters</option>
218 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => ( 218 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => (
219 <option value={e.id} key={i + 1}>{e.name}</option> 219 <option value={e.id} key={i + 1}>{e.name}</option>
@@ -222,9 +222,9 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
222 </div> 222 </div>
223 <div id='profileboard-top'> 223 <div id='profileboard-top'>
224 <span><span>Map Name</span><img src={SortIcon} alt="" /></span> 224 <span><span>Map Name</span><img src={SortIcon} alt="" /></span>
225 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span> 225 <span style={{ justifyContent: "center" }}><span>Portals</span><img src={SortIcon} alt="" /></span>
226 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span> 226 <span style={{ justifyContent: "center" }}><span>WRΔ </span><img src={SortIcon} alt="" /></span>
227 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span> 227 <span style={{ justifyContent: "center" }}><span>Time</span><img src={SortIcon} alt="" /></span>
228 <span> </span> 228 <span> </span>
229 <span><span>Rank</span><img src={SortIcon} alt="" /></span> 229 <span><span>Rank</span><img src={SortIcon} alt="" /></span>
230 <span><span>Date</span><img src={SortIcon} alt="" /></span> 230 <span><span>Date</span><img src={SortIcon} alt="" /></span>
@@ -239,7 +239,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
239 }); 239 });
240 } 240 }
241 }} 241 }}
242 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button> 242 ><i className='triangle' style={{ position: "relative", left: "-5px", }}></i> </button>
243 <span>{pageNumber}/{pageMax}</span> 243 <span>{pageNumber}/{pageMax}</span>
244 <button onClick={() => { 244 <button onClick={() => {
245 if (pageNumber !== pageMax) { 245 if (pageNumber !== pageMax) {
@@ -250,7 +250,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
250 }); 250 });
251 } 251 }
252 }} 252 }}
253 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button> 253 ><i className='triangle' style={{ position: "relative", left: "5px", transform: "rotate(180deg)" }}></i> </button>
254 </div> 254 </div>
255 </div> 255 </div>
256 </div> 256 </div>
@@ -272,7 +272,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
272 272
273 <span style={{ display: "grid" }}>{e.score_count}</span> 273 <span style={{ display: "grid" }}>{e.score_count}</span>
274 274
275 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : `-`}</span> 275 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : "-"}</span>
276 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span> 276 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span>
277 <span> </span> 277 <span> </span>
278 {i === 0 ? <span>#{r.placement}</span> : <span> </span>} 278 {i === 0 ? <span>#{r.placement}</span> : <span> </span>}
@@ -300,7 +300,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
300 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id) 300 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id)
301 .map((r, index) => { 301 .map((r, index) => {
302 if (Math.ceil((index + 1) / 20) === pageNumber) { 302 if (Math.ceil((index + 1) / 20) === pageNumber) {
303 let record = profile.records.find((e) => e.map_id === r.id); 303 const record = profile.records.find((e) => e.map_id === r.id);
304 return record === undefined ? ( 304 return record === undefined ? (
305 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}> 305 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}>
306 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 306 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
@@ -318,7 +318,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
318 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 318 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
319 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 319 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
320 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span> 320 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span>
321 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count > 0 ? `+${record!.scores[i].score_count - record!.map_wr_count}` : `-`}</span> 321 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count > 0 ? `+${record!.scores[i].score_count - record!.map_wr_count}` : "-"}</span>
322 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span> 322 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span>
323 <span> </span> 323 <span> </span>
324 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>} 324 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>}
diff --git a/frontend/src/pages/Rankings.tsx b/frontend/src/pages/Rankings.tsx
index 71aa427..12dcab4 100644
--- a/frontend/src/pages/Rankings.tsx
+++ b/frontend/src/pages/Rankings.tsx
@@ -8,140 +8,140 @@ import { API } from "@api/Api";
8import "@css/Rankings.css"; 8import "@css/Rankings.css";
9 9
10const Rankings: React.FC = () => { 10const Rankings: React.FC = () => {
11 const [leaderboardData, setLeaderboardData] = React.useState<Ranking | SteamRanking>(); 11 const [leaderboardData, setLeaderboardData] = React.useState<Ranking | SteamRanking>();
12 const [currentLeaderboard, setCurrentLeaderboard] = React.useState<RankingType[] | SteamRankingType[]>(); 12 const [currentLeaderboard, setCurrentLeaderboard] = React.useState<RankingType[] | SteamRankingType[]>();
13 enum LeaderboardTypes { 13 enum LeaderboardTypes {
14 official, 14 official,
15 unofficial 15 unofficial
16 }
17 const [currentRankingType, setCurrentRankingType] = React.useState<LeaderboardTypes>(LeaderboardTypes.official);
18
19 const [leaderboardLoad, setLeaderboardLoad] = React.useState<boolean>(false);
20
21 enum RankingCategories {
22 rankings_overall,
23 rankings_multiplayer,
24 rankings_singleplayer
25 }
26 const [currentLeaderboardType, setCurrentLeaderboardType] = React.useState<RankingCategories>(RankingCategories.rankings_singleplayer);
27 const [load, setLoad] = React.useState<boolean>(false);
28
29 const _fetch_rankings = async () => {
30 setLeaderboardLoad(false);
31 const rankings = await API.get_official_rankings();
32 setLeaderboardData(rankings);
33 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) {
34 setCurrentLeaderboard(rankings.rankings_singleplayer)
35 } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) {
36 setCurrentLeaderboard(rankings.rankings_multiplayer)
37 } else {
38 setCurrentLeaderboard(rankings.rankings_overall)
16 } 39 }
17 const [currentRankingType, setCurrentRankingType] = React.useState<LeaderboardTypes>(LeaderboardTypes.official); 40 setLoad(true);
18 41 setLeaderboardLoad(true);
19 const [leaderboardLoad, setLeaderboardLoad] = React.useState<boolean>(false); 42 }
20 43
21 enum RankingCategories { 44 const __dev_fetch_unofficial_rankings = async () => {
22 rankings_overall, 45 try {
23 rankings_multiplayer, 46 setLeaderboardLoad(false);
24 rankings_singleplayer 47 const rankings = await API.get_unofficial_rankings();
48 setLeaderboardData(rankings);
49 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) {
50 // console.log(_sort_rankings_steam(unofficialRanking.rankings_singleplayer))
51 setCurrentLeaderboard(rankings.rankings_singleplayer)
52 } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) {
53 setCurrentLeaderboard(rankings.rankings_multiplayer)
54 } else {
55 setCurrentLeaderboard(rankings.rankings_overall)
56 }
57 setLeaderboardLoad(true);
58 } catch (e) {
59 console.log(e)
25 } 60 }
26 const [currentLeaderboardType, setCurrentLeaderboardType] = React.useState<RankingCategories>(RankingCategories.rankings_singleplayer); 61 }
27 const [load, setLoad] = React.useState<boolean>(false); 62
28 63 const _set_current_leaderboard = (ranking_cat: RankingCategories) => {
29 const _fetch_rankings = async () => { 64 if (ranking_cat == RankingCategories.rankings_singleplayer) {
30 setLeaderboardLoad(false); 65 setCurrentLeaderboard(leaderboardData!.rankings_singleplayer);
31 const rankings = await API.get_official_rankings(); 66 } else if (ranking_cat == RankingCategories.rankings_multiplayer) {
32 setLeaderboardData(rankings); 67 setCurrentLeaderboard(leaderboardData!.rankings_multiplayer);
33 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { 68 } else {
34 setCurrentLeaderboard(rankings.rankings_singleplayer) 69 setCurrentLeaderboard(leaderboardData!.rankings_overall);
35 } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) {
36 setCurrentLeaderboard(rankings.rankings_multiplayer)
37 } else {
38 setCurrentLeaderboard(rankings.rankings_overall)
39 }
40 setLoad(true);
41 setLeaderboardLoad(true);
42 } 70 }
43 71
44 const __dev_fetch_unofficial_rankings = async () => { 72 setCurrentLeaderboardType(ranking_cat);
45 try { 73 }
46 setLeaderboardLoad(false);
47 const rankings = await API.get_unofficial_rankings();
48 setLeaderboardData(rankings);
49 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) {
50 // console.log(_sort_rankings_steam(unofficialRanking.rankings_singleplayer))
51 setCurrentLeaderboard(rankings.rankings_singleplayer)
52 } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) {
53 setCurrentLeaderboard(rankings.rankings_multiplayer)
54 } else {
55 setCurrentLeaderboard(rankings.rankings_overall)
56 }
57 setLeaderboardLoad(true);
58 } catch (e) {
59 console.log(e)
60 }
61 }
62 74
63 const _set_current_leaderboard = (ranking_cat: RankingCategories) => { 75 const _set_leaderboard_type = (leaderboard_type: LeaderboardTypes) => {
64 if (ranking_cat == RankingCategories.rankings_singleplayer) { 76 if (leaderboard_type == LeaderboardTypes.official) {
65 setCurrentLeaderboard(leaderboardData!.rankings_singleplayer); 77 _fetch_rankings();
66 } else if (ranking_cat == RankingCategories.rankings_multiplayer) { 78 } else {
67 setCurrentLeaderboard(leaderboardData!.rankings_multiplayer);
68 } else {
69 setCurrentLeaderboard(leaderboardData!.rankings_overall);
70 }
71 79
72 setCurrentLeaderboardType(ranking_cat);
73 } 80 }
81 }
74 82
75 const _set_leaderboard_type = (leaderboard_type: LeaderboardTypes) => { 83 useEffect(() => {
76 if (leaderboard_type == LeaderboardTypes.official) { 84 _fetch_rankings();
77 _fetch_rankings(); 85 if (load) {
78 } else { 86 _set_current_leaderboard(RankingCategories.rankings_singleplayer);
79
80 }
81 } 87 }
88 }, [load])
89
90 return (
91 <main>
92 <Helmet>
93 <title>LPHUB | Rankings</title>
94 </Helmet>
95 <section className="nav-container nav-1">
96 <div>
97 <button onClick={() => { _fetch_rankings(); setCurrentRankingType(LeaderboardTypes.official) }} className={`nav-1-btn ${currentRankingType == LeaderboardTypes.official ? "selected" : ""}`}>
98 <span>Official (LPHUB)</span>
99 </button>
100 <button onClick={() => { __dev_fetch_unofficial_rankings(); setCurrentRankingType(LeaderboardTypes.unofficial) }} className={`nav-1-btn ${currentRankingType == LeaderboardTypes.unofficial ? "selected" : ""}`}>
101 <span>Unofficial (Steam)</span>
102 </button>
103 </div>
104 </section>
105 <section className="nav-container nav-2">
106 <div>
107 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_singleplayer)} className={`nav-2-btn ${currentLeaderboardType == RankingCategories.rankings_singleplayer ? "selected" : ""}`}>
108 <span>Singleplayer</span>
109 </button>
110 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_multiplayer)} className={`nav-2-btn ${currentLeaderboardType == RankingCategories.rankings_multiplayer ? "selected" : ""}`}>
111 <span>Cooperative</span>
112 </button>
113 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_overall)} className={`nav-2-btn ${currentLeaderboardType == RankingCategories.rankings_overall ? "selected" : ""}`}>
114 <span>Overall</span>
115 </button>
116 </div>
117 </section>
118
119 {load ?
120 <section className="rankings-leaderboard">
121 <div className="ranks-container">
122 <div className="leaderboard-entry header">
123 <span>Rank</span>
124 <span>Player</span>
125 <span>Portals</span>
126 </div>
127
128 <div className="splitter"></div>
129
130 {leaderboardLoad && currentLeaderboard?.map((curRankingData, i) => {
131 return <RankingEntry currentLeaderboardType={currentLeaderboardType} curRankingData={curRankingData} key={i}></RankingEntry>
132 })
133 }
82 134
83 useEffect(() => { 135 {leaderboardLoad ? null :
84 _fetch_rankings(); 136 <div style={{ display: "flex", justifyContent: "center", margin: "30px 0px" }}>
85 if (load) { 137 <span className="loader"></span>
86 _set_current_leaderboard(RankingCategories.rankings_singleplayer); 138 </div>
87 } 139 }
88 }, [load]) 140 </div>
89 141 </section>
90 return ( 142 : null}
91 <main> 143 </main>
92 <Helmet> 144 )
93 <title>LPHUB | Rankings</title>
94 </Helmet>
95 <section className="nav-container nav-1">
96 <div>
97 <button onClick={() => { _fetch_rankings(); setCurrentRankingType(LeaderboardTypes.official) }} className={`nav-1-btn ${currentRankingType == LeaderboardTypes.official ? "selected" : ""}`}>
98 <span>Official (LPHUB)</span>
99 </button>
100 <button onClick={() => { __dev_fetch_unofficial_rankings(); setCurrentRankingType(LeaderboardTypes.unofficial) }} className={`nav-1-btn ${currentRankingType == LeaderboardTypes.unofficial ? "selected" : ""}`}>
101 <span>Unofficial (Steam)</span>
102 </button>
103 </div>
104 </section>
105 <section className="nav-container nav-2">
106 <div>
107 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_singleplayer)} className={`nav-2-btn ${currentLeaderboardType == RankingCategories.rankings_singleplayer ? "selected" : ""}`}>
108 <span>Singleplayer</span>
109 </button>
110 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_multiplayer)} className={`nav-2-btn ${currentLeaderboardType == RankingCategories.rankings_multiplayer ? "selected" : ""}`}>
111 <span>Cooperative</span>
112 </button>
113 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_overall)} className={`nav-2-btn ${currentLeaderboardType == RankingCategories.rankings_overall ? "selected" : ""}`}>
114 <span>Overall</span>
115 </button>
116 </div>
117 </section>
118
119 {load ?
120 <section className="rankings-leaderboard">
121 <div className="ranks-container">
122 <div className="leaderboard-entry header">
123 <span>Rank</span>
124 <span>Player</span>
125 <span>Portals</span>
126 </div>
127
128 <div className="splitter"></div>
129
130 {leaderboardLoad && currentLeaderboard?.map((curRankingData, i) => {
131 return <RankingEntry currentLeaderboardType={currentLeaderboardType} curRankingData={curRankingData} key={i}></RankingEntry>
132 })
133 }
134
135 {leaderboardLoad ? null :
136 <div style={{ display: "flex", justifyContent: "center", margin: "30px 0px" }}>
137 <span className="loader"></span>
138 </div>
139 }
140 </div>
141 </section>
142 : null}
143 </main>
144 )
145} 145}
146 146
147export default Rankings; 147export default Rankings;
diff --git a/frontend/src/pages/Rules.tsx b/frontend/src/pages/Rules.tsx
index 9f57b7e..a07c0c1 100644
--- a/frontend/src/pages/Rules.tsx
+++ b/frontend/src/pages/Rules.tsx
@@ -1,41 +1,41 @@
1import React from 'react'; 1import React from "react";
2import ReactMarkdown from 'react-markdown'; 2import ReactMarkdown from "react-markdown";
3import { Helmet } from 'react-helmet'; 3import { Helmet } from "react-helmet";
4 4
5import '@css/Rules.css'; 5import "@css/Rules.css";
6 6
7const Rules: React.FC = () => { 7const Rules: React.FC = () => {
8 8
9 const [rulesText, setRulesText] = React.useState<string>(""); 9 const [rulesText, setRulesText] = React.useState<string>("");
10 10
11 React.useEffect(() => { 11 React.useEffect(() => {
12 const fetchRules = async () => { 12 const fetchRules = async () => {
13 try { 13 try {
14 const response = await fetch( 14 const response = await fetch(
15 'https://raw.githubusercontent.com/pektezol/lphub/main/RULES.md' 15 "https://raw.githubusercontent.com/pektezol/lphub/main/RULES.md"
16 ); 16 );
17 if (!response.ok) { 17 if (!response.ok) {
18 throw new Error('Failed to fetch README'); 18 throw new Error("Failed to fetch README");
19 } 19 }
20 const rulesText = await response.text(); 20 const rulesText = await response.text();
21 setRulesText(rulesText); 21 setRulesText(rulesText);
22 } catch (error) { 22 } catch (error) {
23 console.error('Error fetching Rules:', error); 23 console.error("Error fetching Rules:", error);
24 } 24 }
25 // setRulesText(rulesText) 25 // setRulesText(rulesText)
26 }; 26 };
27 fetchRules(); 27 fetchRules();
28 }, []); 28 }, []);
29 29
30 30
31 return ( 31 return (
32 <main> 32 <main>
33 <Helmet> 33 <Helmet>
34 <title>LPHUB | Rules</title> 34 <title>LPHUB | Rules</title>
35 </Helmet> 35 </Helmet>
36 <ReactMarkdown>{rulesText}</ReactMarkdown> 36 <ReactMarkdown>{rulesText}</ReactMarkdown>
37 </main> 37 </main>
38 ); 38 );
39}; 39};
40 40
41export default Rules; 41export default Rules;
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx
index d43c0c6..33be1f0 100644
--- a/frontend/src/pages/User.tsx
+++ b/frontend/src/pages/User.tsx
@@ -1,15 +1,15 @@
1import React from 'react'; 1import React from "react";
2import { Link, useLocation, useNavigate } from 'react-router-dom'; 2import { Link, useLocation, useNavigate } from "react-router-dom";
3import { Helmet } from 'react-helmet'; 3import { Helmet } from "react-helmet";
4 4
5import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '@images/Images'; 5import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from "@images/Images";
6import { UserProfile } from '@customTypes/Profile'; 6import { UserProfile } from "@customTypes/Profile";
7import { Game, GameChapters } from '@customTypes/Game'; 7import { Game, GameChapters } from "@customTypes/Game";
8import { Map } from '@customTypes/Map'; 8import { Map } from "@customTypes/Map";
9import { API } from '@api/Api'; 9import { API } from "@api/Api";
10import { ticks_to_time } from '@utils/Time'; 10import { ticks_to_time } from "@utils/Time";
11import "@css/Profile.css"; 11import "@css/Profile.css";
12import useMessage from '@hooks/UseMessage'; 12import useMessage from "@hooks/UseMessage";
13 13
14interface UserProps { 14interface UserProps {
15 profile?: UserProfile; 15 profile?: UserProfile;
@@ -162,9 +162,9 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
162 162
163 <select id='select-game' 163 <select id='select-game'
164 onChange={() => { 164 onChange={() => {
165 setGame((document.querySelector('#select-game') as HTMLInputElement).value); 165 setGame((document.querySelector("#select-game") as HTMLInputElement).value);
166 setChapter("0"); 166 setChapter("0");
167 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement; 167 const chapterSelect = document.querySelector("#select-chapter") as HTMLSelectElement;
168 if (chapterSelect) { 168 if (chapterSelect) {
169 chapterSelect.value = "0"; 169 chapterSelect.value = "0";
170 } 170 }
@@ -182,7 +182,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
182 : chapterData === null ? <select></select> : 182 : chapterData === null ? <select></select> :
183 183
184 <select id='select-chapter' 184 <select id='select-chapter'
185 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}> 185 onChange={() => setChapter((document.querySelector("#select-chapter") as HTMLInputElement).value)}>
186 <option value="0" key="0">All Chapters</option> 186 <option value="0" key="0">All Chapters</option>
187 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => ( 187 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => (
188 <option value={e.id} key={i + 1}>{e.name}</option> 188 <option value={e.id} key={i + 1}>{e.name}</option>
@@ -191,9 +191,9 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
191 </div> 191 </div>
192 <div id='profileboard-top'> 192 <div id='profileboard-top'>
193 <span><span>Map Name</span><img src={SortIcon} alt="" /></span> 193 <span><span>Map Name</span><img src={SortIcon} alt="" /></span>
194 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span> 194 <span style={{ justifyContent: "center" }}><span>Portals</span><img src={SortIcon} alt="" /></span>
195 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span> 195 <span style={{ justifyContent: "center" }}><span>WRΔ </span><img src={SortIcon} alt="" /></span>
196 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span> 196 <span style={{ justifyContent: "center" }}><span>Time</span><img src={SortIcon} alt="" /></span>
197 <span> </span> 197 <span> </span>
198 <span><span>Rank</span><img src={SortIcon} alt="" /></span> 198 <span><span>Rank</span><img src={SortIcon} alt="" /></span>
199 <span><span>Date</span><img src={SortIcon} alt="" /></span> 199 <span><span>Date</span><img src={SortIcon} alt="" /></span>
@@ -208,7 +208,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
208 }); 208 });
209 } 209 }
210 }} 210 }}
211 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button> 211 ><i className='triangle' style={{ position: "relative", left: "-5px", }}></i> </button>
212 <span>{pageNumber}/{pageMax}</span> 212 <span>{pageNumber}/{pageMax}</span>
213 <button onClick={() => { 213 <button onClick={() => {
214 if (pageNumber !== pageMax) { 214 if (pageNumber !== pageMax) {
@@ -219,7 +219,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
219 }); 219 });
220 } 220 }
221 }} 221 }}
222 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button> 222 ><i className='triangle' style={{ position: "relative", left: "5px", transform: "rotate(180deg)" }}></i> </button>
223 </div> 223 </div>
224 </div> 224 </div>
225 </div> 225 </div>
@@ -241,7 +241,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
241 241
242 <span style={{ display: "grid" }}>{e.score_count}</span> 242 <span style={{ display: "grid" }}>{e.score_count}</span>
243 243
244 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : `-`}</span> 244 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : "-"}</span>
245 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span> 245 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span>
246 <span> </span> 246 <span> </span>
247 {i === 0 ? <span>#{r.placement}</span> : <span> </span>} 247 {i === 0 ? <span>#{r.placement}</span> : <span> </span>}
@@ -268,7 +268,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
268 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id) 268 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id)
269 .map((r, index) => { 269 .map((r, index) => {
270 if (Math.ceil((index + 1) / 20) === pageNumber) { 270 if (Math.ceil((index + 1) / 20) === pageNumber) {
271 let record = user.records.find((e) => e.map_id === r.id); 271 const record = user.records.find((e) => e.map_id === r.id);
272 return record === undefined ? ( 272 return record === undefined ? (
273 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}> 273 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}>
274 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 274 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
@@ -286,7 +286,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
286 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 286 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
287 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 287 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
288 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span> 288 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span>
289 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count > 0 ? `+${record!.scores[i].score_count - record!.map_wr_count}` : `-`}</span> 289 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count > 0 ? `+${record!.scores[i].score_count - record!.map_wr_count}` : "-"}</span>
290 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span> 290 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span>
291 <span> </span> 291 <span> </span>
292 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>} 292 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>}