diff options
| author | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2025-10-22 13:59:12 +0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-22 12:59:12 +0300 |
| commit | 69aeb7889ac136a8e4fbe7de1330298e30345479 (patch) | |
| tree | 6b2cd2d420105dc7ffad3c3649df359f634cae77 /frontend/src/pages | |
| parent | feat/rankings: update wr for 3 maps (#279) (diff) | |
| download | lphub-69aeb7889ac136a8e4fbe7de1330298e30345479.tar.gz lphub-69aeb7889ac136a8e4fbe7de1330298e30345479.tar.bz2 lphub-69aeb7889ac136a8e4fbe7de1330298e30345479.zip | |
feat/frontend: switch to vite, update node to v22 (#281)
Diffstat (limited to 'frontend/src/pages')
| -rw-r--r-- | frontend/src/pages/About.tsx | 60 | ||||
| -rw-r--r-- | frontend/src/pages/Games.tsx | 64 | ||||
| -rw-r--r-- | frontend/src/pages/Homepage.tsx | 32 | ||||
| -rw-r--r-- | frontend/src/pages/Maps.tsx | 22 | ||||
| -rw-r--r-- | frontend/src/pages/Profile.tsx | 46 | ||||
| -rw-r--r-- | frontend/src/pages/Rankings.tsx | 248 | ||||
| -rw-r--r-- | frontend/src/pages/Rules.tsx | 62 | ||||
| -rw-r--r-- | frontend/src/pages/User.tsx | 44 |
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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import ReactMarkdown from 'react-markdown'; | 2 | import ReactMarkdown from "react-markdown"; |
| 3 | import { Helmet } from 'react-helmet'; | 3 | import { Helmet } from "react-helmet"; |
| 4 | 4 | ||
| 5 | import '@css/About.css'; | 5 | import "@css/About.css"; |
| 6 | 6 | ||
| 7 | const About: React.FC = () => { | 7 | const 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 | ||
| 40 | export default About; | 40 | export 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import { Helmet } from 'react-helmet'; | 2 | import { Helmet } from "react-helmet"; |
| 3 | 3 | ||
| 4 | import GameEntry from '@components/GameEntry'; | 4 | import GameEntry from "@components/GameEntry"; |
| 5 | import { Game } from '@customTypes/Game'; | 5 | import { Game } from "@customTypes/Game"; |
| 6 | import "@css/Maps.css" | 6 | import "@css/Maps.css" |
| 7 | 7 | ||
| 8 | interface GamesProps { | 8 | interface GamesProps { |
| 9 | games: Game[]; | 9 | games: Game[]; |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | const Games: React.FC<GamesProps> = ({ games }) => { | 12 | const 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 | ||
| 46 | export default Games; | 46 | export 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import { Helmet } from 'react-helmet'; | 2 | import { Helmet } from "react-helmet"; |
| 3 | 3 | ||
| 4 | const Homepage: React.FC = () => { | 4 | const 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 | ||
| 22 | export default Homepage; | 22 | export 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import { Link, useLocation } from 'react-router-dom'; | 2 | import { Link, useLocation } from "react-router-dom"; |
| 3 | import { Helmet } from 'react-helmet'; | 3 | import { Helmet } from "react-helmet"; |
| 4 | 4 | ||
| 5 | import { PortalIcon, FlagIcon, ChatIcon } from '@images/Images'; | 5 | import { PortalIcon, FlagIcon, ChatIcon } from "@images/Images"; |
| 6 | import Summary from '@components/Summary'; | 6 | import Summary from "@components/Summary"; |
| 7 | import Leaderboards from '@components/Leaderboards'; | 7 | import Leaderboards from "@components/Leaderboards"; |
| 8 | import Discussions from '@components/Discussions'; | 8 | import Discussions from "@components/Discussions"; |
| 9 | import ModMenu from '@components/ModMenu'; | 9 | import ModMenu from "@components/ModMenu"; |
| 10 | import { MapDiscussions, MapLeaderboard, MapSummary } from '@customTypes/Map'; | 10 | import { MapDiscussions, MapLeaderboard, MapSummary } from "@customTypes/Map"; |
| 11 | import { API } from '@api/Api'; | 11 | import { API } from "@api/Api"; |
| 12 | import "@css/Maps.css"; | 12 | import "@css/Maps.css"; |
| 13 | 13 | ||
| 14 | interface MapProps { | 14 | interface 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import { Link, useNavigate } from 'react-router-dom'; | 2 | import { Link, useNavigate } from "react-router-dom"; |
| 3 | import { Helmet } from 'react-helmet'; | 3 | import { Helmet } from "react-helmet"; |
| 4 | 4 | ||
| 5 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon, DeleteIcon } from '@images/Images'; | 5 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon, DeleteIcon } from "@images/Images"; |
| 6 | import { UserProfile } from '@customTypes/Profile'; | 6 | import { UserProfile } from "@customTypes/Profile"; |
| 7 | import { Game, GameChapters } from '@customTypes/Game'; | 7 | import { Game, GameChapters } from "@customTypes/Game"; |
| 8 | import { Map } from '@customTypes/Map'; | 8 | import { Map } from "@customTypes/Map"; |
| 9 | import { ticks_to_time } from '@utils/Time'; | 9 | import { ticks_to_time } from "@utils/Time"; |
| 10 | import "@css/Profile.css"; | 10 | import "@css/Profile.css"; |
| 11 | import { API } from '@api/Api'; | 11 | import { API } from "@api/Api"; |
| 12 | import useConfirm from '@hooks/UseConfirm'; | 12 | import useConfirm from "@hooks/UseConfirm"; |
| 13 | import useMessage from '@hooks/UseMessage'; | 13 | import useMessage from "@hooks/UseMessage"; |
| 14 | import useMessageLoad from "@hooks/UseMessageLoad"; | 14 | import useMessageLoad from "@hooks/UseMessageLoad"; |
| 15 | 15 | ||
| 16 | interface ProfileProps { | 16 | interface 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"; | |||
| 8 | import "@css/Rankings.css"; | 8 | import "@css/Rankings.css"; |
| 9 | 9 | ||
| 10 | const Rankings: React.FC = () => { | 10 | const 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 | ||
| 147 | export default Rankings; | 147 | export 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import ReactMarkdown from 'react-markdown'; | 2 | import ReactMarkdown from "react-markdown"; |
| 3 | import { Helmet } from 'react-helmet'; | 3 | import { Helmet } from "react-helmet"; |
| 4 | 4 | ||
| 5 | import '@css/Rules.css'; | 5 | import "@css/Rules.css"; |
| 6 | 6 | ||
| 7 | const Rules: React.FC = () => { | 7 | const 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 | ||
| 41 | export default Rules; | 41 | export 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import { Link, useLocation, useNavigate } from 'react-router-dom'; | 2 | import { Link, useLocation, useNavigate } from "react-router-dom"; |
| 3 | import { Helmet } from 'react-helmet'; | 3 | import { Helmet } from "react-helmet"; |
| 4 | 4 | ||
| 5 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '@images/Images'; | 5 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from "@images/Images"; |
| 6 | import { UserProfile } from '@customTypes/Profile'; | 6 | import { UserProfile } from "@customTypes/Profile"; |
| 7 | import { Game, GameChapters } from '@customTypes/Game'; | 7 | import { Game, GameChapters } from "@customTypes/Game"; |
| 8 | import { Map } from '@customTypes/Map'; | 8 | import { Map } from "@customTypes/Map"; |
| 9 | import { API } from '@api/Api'; | 9 | import { API } from "@api/Api"; |
| 10 | import { ticks_to_time } from '@utils/Time'; | 10 | import { ticks_to_time } from "@utils/Time"; |
| 11 | import "@css/Profile.css"; | 11 | import "@css/Profile.css"; |
| 12 | import useMessage from '@hooks/UseMessage'; | 12 | import useMessage from "@hooks/UseMessage"; |
| 13 | 13 | ||
| 14 | interface UserProps { | 14 | interface 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>} |