aboutsummaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/App.tsx32
-rw-r--r--frontend/src/api/Api.ts18
-rw-r--r--frontend/src/api/Auth.ts2
-rw-r--r--frontend/src/api/Games.ts2
-rw-r--r--frontend/src/api/Maps.ts4
-rw-r--r--frontend/src/api/Rankings.ts4
-rw-r--r--frontend/src/api/User.ts4
-rw-r--r--frontend/src/components/ConfirmDialog.tsx32
-rw-r--r--frontend/src/components/Discussions.tsx16
-rw-r--r--frontend/src/components/GameCategory.tsx22
-rw-r--r--frontend/src/components/GameEntry.tsx6
-rw-r--r--frontend/src/components/Leaderboards.tsx144
-rw-r--r--frontend/src/components/Login.tsx10
-rw-r--r--frontend/src/components/MapEntry.tsx10
-rw-r--r--frontend/src/components/MessageDialog.tsx30
-rw-r--r--frontend/src/components/MessageDialogLoad.tsx30
-rw-r--r--frontend/src/components/ModMenu.tsx18
-rw-r--r--frontend/src/components/RankingEntry.tsx58
-rw-r--r--frontend/src/components/Sidebar.tsx42
-rw-r--r--frontend/src/components/Summary.tsx12
-rw-r--r--frontend/src/components/UploadRunDialog.tsx22
-rw-r--r--frontend/src/hooks/UseConfirm.tsx56
-rw-r--r--frontend/src/hooks/UseMessage.tsx52
-rw-r--r--frontend/src/hooks/UseMessageLoad.tsx48
-rw-r--r--frontend/src/images/Images.tsx40
-rw-r--r--frontend/src/index.tsx8
-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
-rw-r--r--frontend/src/react-app-env.d.ts2
-rw-r--r--frontend/src/types/Content.ts4
-rw-r--r--frontend/src/types/Game.ts2
-rw-r--r--frontend/src/types/Map.ts6
-rw-r--r--frontend/src/types/MapNames.ts220
-rw-r--r--frontend/src/utils/Jwt.ts20
-rw-r--r--frontend/src/utils/Time.ts30
41 files changed, 789 insertions, 795 deletions
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 @@
1import React from 'react'; 1import React from "react";
2import { Routes, Route } from "react-router-dom"; 2import { Routes, Route } from "react-router-dom";
3import { Helmet } from "react-helmet"; 3import { Helmet } from "react-helmet";
4 4
5import { UserProfile } from '@customTypes/Profile'; 5import { UserProfile } from "@customTypes/Profile";
6import Sidebar from './components/Sidebar'; 6import Sidebar from "./components/Sidebar";
7import "./App.css"; 7import "./App.css";
8 8
9import Profile from '@pages/Profile'; 9import Profile from "@pages/Profile";
10import Games from '@pages/Games'; 10import Games from "@pages/Games";
11import Maps from '@pages/Maps'; 11import Maps from "@pages/Maps";
12import User from '@pages/User'; 12import User from "@pages/User";
13import Homepage from '@pages/Homepage'; 13import Homepage from "@pages/Homepage";
14import UploadRunDialog from './components/UploadRunDialog'; 14import UploadRunDialog from "./components/UploadRunDialog";
15import Rules from '@pages/Rules'; 15import Rules from "@pages/Rules";
16import About from '@pages/About'; 16import About from "@pages/About";
17import { Game } from '@customTypes/Game'; 17import { Game } from "@customTypes/Game";
18import { API } from './api/Api'; 18import { API } from "./api/Api";
19import Maplist from '@pages/Maplist'; 19import Maplist from "@pages/Maplist";
20import Rankings from '@pages/Rankings'; 20import Rankings from "@pages/Rankings";
21import { get_user_id_from_token, get_user_mod_from_token } from './utils/Jwt'; 21import { get_user_id_from_token, get_user_mod_from_token } from "./utils/Jwt";
22 22
23const App: React.FC = () => { 23const App: React.FC = () => {
24 const [token, setToken] = React.useState<string | undefined>(undefined); 24 const [token, setToken] = React.useState<string | undefined>(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 @@
1import { MapDiscussionCommentContent, MapDiscussionContent, ModMenuContent } from '@customTypes/Content'; 1import { MapDiscussionContent, ModMenuContent } from "@customTypes/Content";
2import { delete_token, get_token } from '@api/Auth'; 2import { delete_token, get_token } from "@api/Auth";
3import { get_user, get_profile, post_profile } from '@api/User'; 3import { get_user, get_profile, post_profile } from "@api/User";
4import { get_games, get_chapters, get_games_chapters, get_game_maps, get_search } from '@api/Games'; 4import { get_games, get_chapters, get_games_chapters, get_game_maps, get_search } from "@api/Games";
5import { get_official_rankings, get_unofficial_rankings } from '@api/Rankings'; 5import { get_official_rankings, get_unofficial_rankings } from "@api/Rankings";
6import { 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'; 6import { 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";
7import { delete_map_summary, post_map_summary, put_map_image, put_map_summary } from '@api/Mod'; 7import { delete_map_summary, post_map_summary, put_map_image, put_map_summary } from "@api/Mod";
8import { UploadRunContent } from '@customTypes/Content'; 8import { UploadRunContent } from "@customTypes/Content";
9 9
10// add new api call function entries here 10// add new api call function entries here
11// example usage: API.get_games(); 11// example usage: API.get_games();
@@ -49,7 +49,7 @@ export const API = {
49 delete_map_summary: (token: string, map_id: string, route_id: number) => delete_map_summary(token, map_id, route_id), 49 delete_map_summary: (token: string, map_id: string, route_id: number) => delete_map_summary(token, map_id, route_id),
50}; 50};
51 51
52const BASE_API_URL: string = "/api/v1/" 52const BASE_API_URL: string = "https://lp.portal2.sr/api/v1/"
53 53
54export function url(path: string): string { 54export function url(path: string): string {
55 return BASE_API_URL + path; 55 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";
2import { url } from "@api/Api"; 2import { url } from "@api/Api";
3 3
4export const get_token = async (): Promise<string | undefined> => { 4export const get_token = async (): Promise<string | undefined> => {
5 const response = await axios.get(url(`token`)) 5 const response = await axios.get(url("token"))
6 if (!response.data.success) { 6 if (!response.data.success) {
7 return undefined; 7 return undefined;
8 } 8 }
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";
6import { Search } from "@customTypes/Search"; 6import { Search } from "@customTypes/Search";
7 7
8export const get_games = async (): Promise<Game[]> => { 8export const get_games = async (): Promise<Game[]> => {
9 const response = await axios.get(url(`games`)) 9 const response = await axios.get(url("games"))
10 return response.data.data; 10 return response.data.data;
11}; 11};
12 12
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
17 // map the kind of leaderboard 17 // map the kind of leaderboard
18 data.records = data.records.map((record: any) => { 18 data.records = data.records.map((record: any) => {
19 if (record.host && record.partner) { 19 if (record.host && record.partner) {
20 return { ...record, kind: 'multiplayer' }; 20 return { ...record, kind: "multiplayer" };
21 } else { 21 } else {
22 return { ...record, kind: 'singleplayer' }; 22 return { ...record, kind: "singleplayer" };
23 } 23 }
24 }); 24 });
25 return data; 25 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";
3import { Ranking, SteamRanking } from "@customTypes/Ranking"; 3import { Ranking, SteamRanking } from "@customTypes/Ranking";
4 4
5export const get_official_rankings = async (): Promise<Ranking> => { 5export const get_official_rankings = async (): Promise<Ranking> => {
6 const response = await axios.get(url(`rankings/lphub`)); 6 const response = await axios.get(url("rankings/lphub"));
7 return response.data.data; 7 return response.data.data;
8}; 8};
9 9
10export const get_unofficial_rankings = async (): Promise<SteamRanking> => { 10export const get_unofficial_rankings = async (): Promise<SteamRanking> => {
11 const response = await axios.get(url(`rankings/steam`)); 11 const response = await axios.get(url("rankings/steam"));
12 return response.data.data; 12 return response.data.data;
13}; 13};
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<UserProfile> => {
8}; 8};
9 9
10export const get_profile = async (token: string): Promise<UserProfile> => { 10export const get_profile = async (token: string): Promise<UserProfile> => {
11 const response = await axios.get(url(`profile`), { 11 const response = await axios.get(url("profile"), {
12 headers: { 12 headers: {
13 "Authorization": token, 13 "Authorization": token,
14 } 14 }
@@ -17,7 +17,7 @@ export const get_profile = async (token: string): Promise<UserProfile> => {
17}; 17};
18 18
19export const post_profile = async (token: string) => { 19export const post_profile = async (token: string) => {
20 const _ = await axios.post(url(`profile`), {}, { 20 await axios.post(url("profile"), {}, {
21 headers: { 21 headers: {
22 "Authorization": token, 22 "Authorization": token,
23 } 23 }
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 @@
1import React from 'react'; 1import React from "react";
2 2
3import "@css/Dialog.css" 3import "@css/Dialog.css"
4 4
@@ -10,22 +10,22 @@ interface ConfirmDialogProps {
10}; 10};
11 11
12const ConfirmDialog: React.FC<ConfirmDialogProps> = ({ title, subtitle, onConfirm, onCancel }) => { 12const ConfirmDialog: React.FC<ConfirmDialogProps> = ({ title, subtitle, onConfirm, onCancel }) => {
13 return ( 13 return (
14 <div className='dimmer'> 14 <div className='dimmer'>
15 <div className='dialog'> 15 <div className='dialog'>
16 <div className='dialog-element dialog-header'> 16 <div className='dialog-element dialog-header'>
17 <span>{title}</span> 17 <span>{title}</span>
18 </div>
19 <div className='dialog-element dialog-description'>
20 <span>{subtitle}</span>
21 </div>
22 <div className='dialog-element dialog-btns-container'>
23 <button onClick={onCancel}>Cancel</button>
24 <button onClick={onConfirm}>Confirm</button>
25 </div>
26 </div>
27 </div> 18 </div>
28 ) 19 <div className='dialog-element dialog-description'>
20 <span>{subtitle}</span>
21 </div>
22 <div className='dialog-element dialog-btns-container'>
23 <button onClick={onCancel}>Cancel</button>
24 <button onClick={onConfirm}>Confirm</button>
25 </div>
26 </div>
27 </div>
28 )
29}; 29};
30 30
31export default ConfirmDialog; 31export 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 @@
1import React from 'react'; 1import React from "react";
2 2
3import { MapDiscussion, MapDiscussions, MapDiscussionsDetail } from '@customTypes/Map'; 3import { MapDiscussion, MapDiscussions, MapDiscussionsDetail } from "@customTypes/Map";
4import { MapDiscussionCommentContent, MapDiscussionContent } from '@customTypes/Content'; 4import { MapDiscussionContent } from "@customTypes/Content";
5import { time_ago } from '@utils/Time'; 5import { time_ago } from "@utils/Time";
6import { API } from '@api/Api'; 6import { API } from "@api/Api";
7import "@css/Maps.css" 7import "@css/Maps.css"
8import { Link } from 'react-router-dom'; 8import { Link } from "react-router-dom";
9import useConfirm from '@hooks/UseConfirm'; 9import useConfirm from "@hooks/UseConfirm";
10 10
11interface DiscussionsProps { 11interface DiscussionsProps {
12 token?: string 12 token?: string
@@ -141,7 +141,7 @@ const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, map
141 data ? 141 data ?
142 (<> 142 (<>
143 {data.discussions.filter(f => f.title.includes(discussionSearch)).sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()) 143 {data.discussions.filter(f => f.title.includes(discussionSearch)).sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
144 .map((e, i) => ( 144 .map((e) => (
145 <div id='discussion-post'> 145 <div id='discussion-post'>
146 <button key={e.id} onClick={() => _open_map_discussion(e.id)}> 146 <button key={e.id} onClick={() => _open_map_discussion(e.id)}>
147 <span>{e.title}</span> 147 <span>{e.title}</span>
diff --git a/frontend/src/components/GameCategory.tsx b/frontend/src/components/GameCategory.tsx
index d8879ef..0d76d3e 100644
--- a/frontend/src/components/GameCategory.tsx
+++ b/frontend/src/components/GameCategory.tsx
@@ -1,7 +1,7 @@
1import React from 'react'; 1import React from "react";
2import { Link } from "react-router-dom"; 2import { Link } from "react-router-dom";
3 3
4import { Game, GameCategoryPortals } from '@customTypes/Game'; 4import { Game, GameCategoryPortals } from "@customTypes/Game";
5import "@css/Games.css" 5import "@css/Games.css"
6 6
7interface GameCategoryProps { 7interface GameCategoryProps {
@@ -10,15 +10,15 @@ interface GameCategoryProps {
10} 10}
11 11
12const GameCategory: React.FC<GameCategoryProps> = ({cat, game}) => { 12const GameCategory: React.FC<GameCategoryProps> = ({cat, game}) => {
13 return ( 13 return (
14 <Link className="games-page-item-body-item" to={"/games/" + game.id + "?cat=" + cat.category.id}> 14 <Link className="games-page-item-body-item" to={"/games/" + game.id + "?cat=" + cat.category.id}>
15 <div> 15 <div>
16 <span className='games-page-item-body-item-title'>{cat.category.name}</span> 16 <span className='games-page-item-body-item-title'>{cat.category.name}</span>
17 <br /> 17 <br />
18 <span className='games-page-item-body-item-num'>{cat.portal_count}</span> 18 <span className='games-page-item-body-item-num'>{cat.portal_count}</span>
19 </div> 19 </div>
20 </Link> 20 </Link>
21 ) 21 )
22} 22}
23 23
24export default GameCategory; 24export default GameCategory;
diff --git a/frontend/src/components/GameEntry.tsx b/frontend/src/components/GameEntry.tsx
index 3bd2842..454efb1 100644
--- a/frontend/src/components/GameEntry.tsx
+++ b/frontend/src/components/GameEntry.tsx
@@ -1,10 +1,10 @@
1import React from 'react'; 1import React from "react";
2import { Link } from "react-router-dom"; 2import { Link } from "react-router-dom";
3 3
4import { Game, GameCategoryPortals } from '@customTypes/Game'; 4import { Game, GameCategoryPortals } from "@customTypes/Game";
5import "@css/Games.css" 5import "@css/Games.css"
6 6
7import GameCategory from '@components/GameCategory'; 7import GameCategory from "@components/GameCategory";
8 8
9interface GameEntryProps { 9interface GameEntryProps {
10 game: Game; 10 game: Game;
diff --git a/frontend/src/components/Leaderboards.tsx b/frontend/src/components/Leaderboards.tsx
index fb614fa..5d4045a 100644
--- a/frontend/src/components/Leaderboards.tsx
+++ b/frontend/src/components/Leaderboards.tsx
@@ -1,9 +1,9 @@
1import React from 'react'; 1import React from "react";
2import { Link, useNavigate } from 'react-router-dom'; 2import { Link, useNavigate } from "react-router-dom";
3 3
4import { DownloadIcon, ThreedotIcon } from '@images/Images'; 4import { DownloadIcon, ThreedotIcon } from "@images/Images";
5import { MapLeaderboard } from '@customTypes/Map'; 5import { MapLeaderboard } from "@customTypes/Map";
6import { ticks_to_time, time_ago } from '@utils/Time'; 6import { ticks_to_time, time_ago } from "@utils/Time";
7import { API } from "@api/Api"; 7import { API } from "@api/Api";
8import useMessage from "@hooks/UseMessage"; 8import useMessage from "@hooks/UseMessage";
9import "@css/Maps.css" 9import "@css/Maps.css"
@@ -46,80 +46,80 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
46 }; 46 };
47 47
48 return ( 48 return (
49 <div> 49 <div>
50 {MessageDialogComponent} 50 {MessageDialogComponent}
51 <section id='section6' className='summary2'> 51 <section id='section6' className='summary2'>
52
53 <div id='leaderboard-top'
54 style={data.map.is_coop ? { gridTemplateColumns: "7.5% 40% 7.5% 15% 15% 15%" } : { gridTemplateColumns: "7.5% 30% 10% 20% 17.5% 15%" }}
55 >
56 <span>Place</span>
57 52
58 {data.map.is_coop ? ( 53 <div id='leaderboard-top'
59 <div id='runner'> 54 style={data.map.is_coop ? { gridTemplateColumns: "7.5% 40% 7.5% 15% 15% 15%" } : { gridTemplateColumns: "7.5% 30% 10% 20% 17.5% 15%" }}
60 <span>Blue</span> 55 >
61 <span>Orange</span> 56 <span>Place</span>
62 </div> 57
63 ) : ( 58 {data.map.is_coop ? (
64 <span>Runner</span> 59 <div id='runner'>
65 )} 60 <span>Blue</span>
66 61 <span>Orange</span>
67 <span>Portals</span> 62 </div>
68 <span>Time</span> 63 ) : (
69 <span>Date</span> 64 <span>Runner</span>
70 <div id='page-number'> 65 )}
71 <div> 66
72 67 <span>Portals</span>
73 <button onClick={() => pageNumber === 1 ? null : setPageNumber(prevPageNumber => prevPageNumber - 1)} 68 <span>Time</span>
74 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button> 69 <span>Date</span>
75 <span>{data.pagination.current_page}/{data.pagination.total_pages}</span> 70 <div id='page-number'>
76 <button onClick={() => pageNumber === data.pagination.total_pages ? null : setPageNumber(prevPageNumber => prevPageNumber + 1)} 71 <div>
77 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button> 72
73 <button onClick={() => pageNumber === 1 ? null : setPageNumber(prevPageNumber => prevPageNumber - 1)}
74 ><i className='triangle' style={{ position: "relative", left: "-5px", }}></i> </button>
75 <span>{data.pagination.current_page}/{data.pagination.total_pages}</span>
76 <button onClick={() => pageNumber === data.pagination.total_pages ? null : setPageNumber(prevPageNumber => prevPageNumber + 1)}
77 ><i className='triangle' style={{ position: "relative", left: "5px", transform: "rotate(180deg)" }}></i> </button>
78 </div>
78 </div> 79 </div>
79 </div> 80 </div>
80 </div> 81 <hr />
81 <hr /> 82 <div id='leaderboard-records'>
82 <div id='leaderboard-records'> 83 {data.records.map((r, index) => (
83 {data.records.map((r, index) => ( 84 <span className='leaderboard-record' key={index}
84 <span className='leaderboard-record' key={index} 85 style={data.map.is_coop ? { gridTemplateColumns: "3% 4.5% 40% 4% 3.5% 15% 15% 14.5%" } : { gridTemplateColumns: "3% 4.5% 30% 4% 6% 20% 17% 15%" }}
85 style={data.map.is_coop ? { gridTemplateColumns: "3% 4.5% 40% 4% 3.5% 15% 15% 14.5%" } : { gridTemplateColumns: "3% 4.5% 30% 4% 6% 20% 17% 15%" }} 86 >
86 > 87 <span>{r.placement}</span>
87 <span>{r.placement}</span> 88 <span> </span>
88 <span> </span> 89 {r.kind === "multiplayer" ? (
89 {r.kind === "multiplayer" ? ( 90 <div>
90 <div>
91 <Link to={`/users/${r.host.steam_id}`}><span><img src={r.host.avatar_link} alt='' /> &nbsp; {r.host.user_name}</span></Link> 91 <Link to={`/users/${r.host.steam_id}`}><span><img src={r.host.avatar_link} alt='' /> &nbsp; {r.host.user_name}</span></Link>
92 <Link to={`/users/${r.partner.steam_id}`}><span><img src={r.partner.avatar_link} alt='' /> &nbsp; {r.partner.user_name}</span></Link> 92 <Link to={`/users/${r.partner.steam_id}`}><span><img src={r.partner.avatar_link} alt='' /> &nbsp; {r.partner.user_name}</span></Link>
93 </div> 93 </div>
94 ) : r.kind === "singleplayer" && ( 94 ) : r.kind === "singleplayer" && (
95 <div> 95 <div>
96 <Link to={`/users/${r.user.steam_id}`}><span><img src={r.user.avatar_link} alt='' /> &nbsp; {r.user.user_name}</span></Link> 96 <Link to={`/users/${r.user.steam_id}`}><span><img src={r.user.avatar_link} alt='' /> &nbsp; {r.user.user_name}</span></Link>
97 </div> 97 </div>
98 )} 98 )}
99 99
100 <span>{r.score_count}</span> 100 <span>{r.score_count}</span>
101 <span> </span> 101 <span> </span>
102 <span className='hover-popup' popup-text={(r.score_time) + " ticks"}>{ticks_to_time(r.score_time)}</span> 102 <span className='hover-popup' popup-text={(r.score_time) + " ticks"}>{ticks_to_time(r.score_time)}</span>
103 <span className='hover-popup' popup-text={r.record_date.replace("T", ' ').split(".")[0]}>{time_ago(new Date(r.record_date.replace("T", " ").replace("Z", "")))}</span> 103 <span className='hover-popup' popup-text={r.record_date.replace("T", " ").split(".")[0]}>{time_ago(new Date(r.record_date.replace("T", " ").replace("Z", "")))}</span>
104 104
105 {r.kind === "multiplayer" ? ( 105 {r.kind === "multiplayer" ? (
106 <span> 106 <span>
107 <button onClick={() => { message("Demo Information", `Host Demo ID: ${r.host_demo_id} \nParnter Demo ID: ${r.partner_demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 107 <button onClick={() => { message("Demo Information", `Host Demo ID: ${r.host_demo_id} \nParnter Demo ID: ${r.partner_demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
108 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.partner_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(160deg) contrast(60%) saturate(1000%)" }} /></button> 108 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.partner_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(160deg) contrast(60%) saturate(1000%)" }} /></button>
109 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.host_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(300deg) contrast(60%) saturate(1000%)" }} /></button> 109 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.host_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(300deg) contrast(60%) saturate(1000%)" }} /></button>
110 </span> 110 </span>
111 ) : r.kind === "singleplayer" && ( 111 ) : r.kind === "singleplayer" && (
112 112
113 <span> 113 <span>
114 <button onClick={() => { message("Demo Information", `Demo ID: ${r.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 114 <button onClick={() => { message("Demo Information", `Demo ID: ${r.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
115 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 115 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
116 </span> 116 </span>
117 )} 117 )}
118 </span> 118 </span>
119 ))} 119 ))}
120 </div> 120 </div>
121 </section> 121 </section>
122 </div> 122 </div>
123 ); 123 );
124}; 124};
125 125
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 @@
1import React from 'react'; 1import React from "react";
2import { Link, useNavigate } from 'react-router-dom'; 2import { Link, useNavigate } from "react-router-dom";
3 3
4import { ExitIcon, UserIcon, LoginIcon } from '@images/Images'; 4import { ExitIcon, UserIcon, LoginIcon } from "@images/Images";
5import { UserProfile } from '@customTypes/Profile'; 5import { UserProfile } from "@customTypes/Profile";
6import { API } from '@api/Api'; 6import { API } from "@api/Api";
7import "@css/Login.css"; 7import "@css/Login.css";
8 8
9interface LoginProps { 9interface 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 @@
1import React from 'react'; 1import React from "react";
2import { Link } from "react-router-dom"; 2import { Link } from "react-router-dom";
3 3
4const MapEntry: React.FC = () => { 4const MapEntry: React.FC = () => {
5 return ( 5 return (
6 <div> 6 <div>
7 7
8 </div> 8 </div>
9 ) 9 )
10} 10}
11 11
12export default MapEntry; 12export 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 @@
1import React from 'react'; 1import React from "react";
2 2
3import "@css/Dialog.css" 3import "@css/Dialog.css"
4 4
@@ -9,21 +9,21 @@ interface MessageDialogProps {
9}; 9};
10 10
11const MessageDialog: React.FC<MessageDialogProps> = ({ title, subtitle, onClose }) => { 11const MessageDialog: React.FC<MessageDialogProps> = ({ title, subtitle, onClose }) => {
12 return ( 12 return (
13 <div className='dimmer'> 13 <div className='dimmer'>
14 <div className='dialog'> 14 <div className='dialog'>
15 <div className='dialog-element dialog-header'> 15 <div className='dialog-element dialog-header'>
16 <span>{title}</span> 16 <span>{title}</span>
17 </div>
18 <div className='dialog-element dialog-description'>
19 <span>{subtitle}</span>
20 </div>
21 <div className='dialog-element dialog-btns-container'>
22 <button onClick={onClose}>Close</button>
23 </div>
24 </div>
25 </div> 17 </div>
26 ) 18 <div className='dialog-element dialog-description'>
19 <span>{subtitle}</span>
20 </div>
21 <div className='dialog-element dialog-btns-container'>
22 <button onClick={onClose}>Close</button>
23 </div>
24 </div>
25 </div>
26 )
27} 27}
28 28
29export default MessageDialog; 29export 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 @@
1import React from 'react'; 1import React from "react";
2 2
3import "@css/Dialog.css" 3import "@css/Dialog.css"
4 4
@@ -8,22 +8,22 @@ interface MessageDialogLoadProps {
8}; 8};
9 9
10const MessageDialogLoad: React.FC<MessageDialogLoadProps> = ({ title, onClose }) => { 10const MessageDialogLoad: React.FC<MessageDialogLoadProps> = ({ title, onClose }) => {
11 return ( 11 return (
12 <div className='dimmer'> 12 <div className='dimmer'>
13 <div className='dialog'> 13 <div className='dialog'>
14 <div className='dialog-element dialog-header'> 14 <div className='dialog-element dialog-header'>
15 <span>{title}</span> 15 <span>{title}</span>
16 </div> 16 </div>
17 <div className='dialog-element dialog-description'> 17 <div className='dialog-element dialog-description'>
18 <div style={{display: "flex", justifyContent: "center"}}> 18 <div style={{display: "flex", justifyContent: "center"}}>
19 <span className="loader"></span> 19 <span className="loader"></span>
20 </div> 20 </div>
21 </div> 21 </div>
22 <div className='dialog-element dialog-btns-container'> 22 <div className='dialog-element dialog-btns-container'>
23 </div>
24 </div>
25 </div> 23 </div>
26 ) 24 </div>
25 </div>
26 )
27} 27}
28 28
29export default MessageDialogLoad; 29export 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 @@
1import React from 'react'; 1import React from "react";
2import ReactMarkdown from 'react-markdown'; 2import ReactMarkdown from "react-markdown";
3import { useNavigate } from 'react-router-dom'; 3import { useNavigate } from "react-router-dom";
4 4
5import { MapSummary } from '@customTypes/Map'; 5import { MapSummary } from "@customTypes/Map";
6import { ModMenuContent } from '@customTypes/Content'; 6import { ModMenuContent } from "@customTypes/Content";
7import { API } from '@api/Api'; 7import { API } from "@api/Api";
8import "@css/ModMenu.css" 8import "@css/ModMenu.css"
9import useConfirm from '@hooks/UseConfirm'; 9import useConfirm from "@hooks/UseConfirm";
10 10
11interface ModMenuProps { 11interface ModMenuProps {
12 token?: string; 12 token?: string;
@@ -55,10 +55,10 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
55 width *= 320 / height; 55 width *= 320 / height;
56 height = 320; 56 height = 320;
57 } 57 }
58 const canvas = document.createElement('canvas'); 58 const canvas = document.createElement("canvas");
59 canvas.width = width; 59 canvas.width = width;
60 canvas.height = height; 60 canvas.height = height;
61 canvas.getContext('2d')!.drawImage(img, 0, 0, width, height); 61 canvas.getContext("2d")!.drawImage(img, 0, 0, width, height);
62 resolve(canvas.toDataURL(file.type, 0.6)); 62 resolve(canvas.toDataURL(file.type, 0.6));
63 }; 63 };
64 } 64 }
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 @@
1import React from 'react'; 1import React from "react";
2import { Link } from "react-router-dom"; 2import { Link } from "react-router-dom";
3import { RankingType, SteamRanking, SteamRankingType } from '@customTypes/Ranking'; 3import { RankingType, SteamRankingType } from "@customTypes/Ranking";
4 4
5enum RankingCategories { 5enum RankingCategories {
6 rankings_overall, 6 rankings_overall,
@@ -14,33 +14,33 @@ interface RankingEntryProps {
14}; 14};
15 15
16const RankingEntry: React.FC<RankingEntryProps> = (prop) => { 16const RankingEntry: React.FC<RankingEntryProps> = (prop) => {
17 if ("placement" in prop.curRankingData) { 17 if ("placement" in prop.curRankingData) {
18 return ( 18 return (
19 <div className='leaderboard-entry'> 19 <div className='leaderboard-entry'>
20 <span>{prop.curRankingData.placement}</span> 20 <span>{prop.curRankingData.placement}</span>
21 <div> 21 <div>
22 <Link to={`/users/${prop.curRankingData.user.steam_id}`}> 22 <Link to={`/users/${prop.curRankingData.user.steam_id}`}>
23 <img src={prop.curRankingData.user.avatar_link}></img> 23 <img src={prop.curRankingData.user.avatar_link}></img>
24 <span>{prop.curRankingData.user.user_name}</span> 24 <span>{prop.curRankingData.user.user_name}</span>
25 </Link> 25 </Link>
26 </div> 26 </div>
27 <span>{prop.curRankingData.total_score}</span> 27 <span>{prop.curRankingData.total_score}</span>
28 </div> 28 </div>
29 ) 29 )
30 } else { 30 } else {
31 return ( 31 return (
32 <div className='leaderboard-entry'> 32 <div className='leaderboard-entry'>
33 <span>{prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_rank : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_rank : prop.curRankingData.overall_rank}</span> 33 <span>{prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_rank : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_rank : prop.curRankingData.overall_rank}</span>
34 <div> 34 <div>
35 <Link to={`/users/${prop.curRankingData.steam_id}`}> 35 <Link to={`/users/${prop.curRankingData.steam_id}`}>
36 <img src={prop.curRankingData.avatar_link}></img> 36 <img src={prop.curRankingData.avatar_link}></img>
37 <span>{prop.curRankingData.user_name}</span> 37 <span>{prop.curRankingData.user_name}</span>
38 </Link> 38 </Link>
39 </div> 39 </div>
40 <span>{prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_score : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_score : prop.curRankingData.overall_score}</span> 40 <span>{prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_score : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_score : prop.curRankingData.overall_score}</span>
41 </div> 41 </div>
42 ) 42 )
43 } 43 }
44} 44}
45 45
46export default RankingEntry; 46export 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 @@
1import React from 'react'; 1import React from "react";
2import { Link, useLocation } from 'react-router-dom'; 2import { Link, useLocation } from "react-router-dom";
3 3
4import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; 4import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from "@images/Images";
5import Login from '@components/Login'; 5import Login from "@components/Login";
6import { UserProfile } from '@customTypes/Profile'; 6import { UserProfile } from "@customTypes/Profile";
7import { Search } from '@customTypes/Search'; 7import { Search } from "@customTypes/Search";
8import { API } from '@api/Api'; 8import { API } from "@api/Api";
9import "@css/Sidebar.css"; 9import "@css/Sidebar.css";
10 10
11interface SidebarProps { 11interface SidebarProps {
@@ -39,7 +39,7 @@ const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUplo
39 }; 39 };
40 40
41 const _handle_sidebar_hide = () => { 41 const _handle_sidebar_hide = () => {
42 var btn = document.querySelectorAll("button.sidebar-button") as NodeListOf<HTMLElement> 42 const btn = document.querySelectorAll("button.sidebar-button") as NodeListOf<HTMLElement>
43 const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf<HTMLElement> 43 const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf<HTMLElement>
44 const side = document.querySelector("#sidebar-list") as HTMLElement; 44 const side = document.querySelector("#sidebar-list") as HTMLElement;
45 const searchbar = document.querySelector("#searchbar") as HTMLInputElement; 45 const searchbar = document.querySelector("#searchbar") as HTMLInputElement;
@@ -140,13 +140,13 @@ const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUplo
140 </div> 140 </div>
141 </div> 141 </div>
142 </Link> 142 </Link>
143 <button id='hamburger-menu' onClick={_toggle_mobile_menu} className={isMobileMenuOpen ? 'open' : ''}> 143 <button id='hamburger-menu' onClick={_toggle_mobile_menu} className={isMobileMenuOpen ? "open" : ""}>
144 <span></span> 144 <span></span>
145 <span></span> 145 <span></span>
146 <span></span> 146 <span></span>
147 </button> 147 </button>
148 </div> 148 </div>
149 <div id='sidebar' className={isMobileMenuOpen ? 'mobile-open' : ''}> 149 <div id='sidebar' className={isMobileMenuOpen ? "mobile-open" : ""}>
150 <Link to="/" tabIndex={-1}> 150 <Link to="/" tabIndex={-1}>
151 <div id='logo'> {/* logo */} 151 <div id='logo'> {/* logo */}
152 <img src={LogoIcon} alt="" height={"80px"} /> 152 <img src={LogoIcon} alt="" height={"80px"} />
@@ -204,7 +204,7 @@ const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUplo
204 </Link> 204 </Link>
205 </div> 205 </div>
206 </div> 206 </div>
207 <div id='search-panel' className={isMobileSearchOpen ? 'mobile-search-open' : ''}> 207 <div id='search-panel' className={isMobileSearchOpen ? "mobile-search-open" : ""}>
208 <input type="text" id='searchbar' placeholder='Search for map or a player...' onChange={(e) => _handle_search_change(e.target.value)} /> 208 <input type="text" id='searchbar' placeholder='Search for map or a player...' onChange={(e) => _handle_search_change(e.target.value)} />
209 <div className='mobile-search-header'> 209 <div className='mobile-search-header'>
210 <button className='mobile-search-close' onClick={_close_mobile_search}>✕</button> 210 <button className='mobile-search-close' onClick={_close_mobile_search}>✕</button>
@@ -220,15 +220,15 @@ const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUplo
220 </Link> 220 </Link>
221 ))} 221 ))}
222 {searchData?.players.map((q, index) => 222 {searchData?.players.map((q, index) =>
223 ( 223 (
224 <Link to={ 224 <Link to={
225 profile && q.steam_id === profile.steam_id ? `/profile` : 225 profile && q.steam_id === profile.steam_id ? "/profile" :
226 `/users/${q.steam_id}` 226 `/users/${q.steam_id}`
227 } className='search-player' key={index} onClick={_close_mobile_search}> 227 } className='search-player' key={index} onClick={_close_mobile_search}>
228 <img src={q.avatar_link} alt='pfp'></img> 228 <img src={q.avatar_link} alt='pfp'></img>
229 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>{q.user_name}</span> 229 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>{q.user_name}</span>
230 </Link> 230 </Link>
231 ))} 231 ))}
232 232
233 </div> 233 </div>
234 </div> 234 </div>
diff --git a/frontend/src/components/Summary.tsx b/frontend/src/components/Summary.tsx
index 7da2f1e..72b5bf0 100644
--- a/frontend/src/components/Summary.tsx
+++ b/frontend/src/components/Summary.tsx
@@ -1,7 +1,7 @@
1import React from 'react'; 1import React from "react";
2import ReactMarkdown from 'react-markdown'; 2import ReactMarkdown from "react-markdown";
3 3
4import { MapSummary } from '@customTypes/Map'; 4import { MapSummary } from "@customTypes/Map";
5import "@css/Maps.css" 5import "@css/Maps.css"
6 6
7interface SummaryProps { 7interface SummaryProps {
@@ -16,7 +16,7 @@ const Summary: React.FC<SummaryProps> = ({ selectedRun, setSelectedRun, data })
16 const [historySelected, setHistorySelected] = React.useState<boolean>(false); 16 const [historySelected, setHistorySelected] = React.useState<boolean>(false);
17 17
18 function _select_run(idx: number, category_id: number) { 18 function _select_run(idx: number, category_id: number) {
19 let r = document.querySelectorAll("button.record"); 19 const r = document.querySelectorAll("button.record");
20 r.forEach(e => (e as HTMLElement).style.backgroundColor = "#2b2e46"); 20 r.forEach(e => (e as HTMLElement).style.backgroundColor = "#2b2e46");
21 (r[idx] as HTMLElement).style.backgroundColor = "#161723" 21 (r[idx] as HTMLElement).style.backgroundColor = "#161723"
22 22
@@ -66,7 +66,7 @@ const Summary: React.FC<SummaryProps> = ({ selectedRun, setSelectedRun, data })
66 style={data.map.image === "" ? { backgroundColor: "#202232" } : {}}> 66 style={data.map.image === "" ? { backgroundColor: "#202232" } : {}}>
67 <img src={data.map.image} alt="" id='category-image'></img> 67 <img src={data.map.image} alt="" id='category-image'></img>
68 <p><span className='portal-count'>{data.summary.routes[selectedRun].history.score_count}</span> 68 <p><span className='portal-count'>{data.summary.routes[selectedRun].history.score_count}</span>
69 {data.summary.routes[selectedRun].history.score_count === 1 ? ` portal` : ` portals`}</p> 69 {data.summary.routes[selectedRun].history.score_count === 1 ? " portal" : " portals"}</p>
70 {data.map.is_coop ? // TODO: make this part dynamic 70 {data.map.is_coop ? // TODO: make this part dynamic
71 ( 71 (
72 <span style={{ gridTemplateColumns: "1fr 1fr 1fr" }}> 72 <span style={{ gridTemplateColumns: "1fr 1fr 1fr" }}>
@@ -109,7 +109,7 @@ const Summary: React.FC<SummaryProps> = ({ selectedRun, setSelectedRun, data })
109 _select_run(index, r.category.id); 109 _select_run(index, r.category.id);
110 }}> 110 }}>
111 <span>{new Date(r.history.date).toLocaleDateString( 111 <span>{new Date(r.history.date).toLocaleDateString(
112 "en-US", { month: 'long', day: 'numeric', year: 'numeric' } 112 "en-US", { month: "long", day: "numeric", year: "numeric" }
113 )}</span> 113 )}</span>
114 <span>{r.history.score_count}</span> 114 <span>{r.history.score_count}</span>
115 <span>{r.history.runner_name}</span> 115 <span>{r.history.runner_name}</span>
diff --git a/frontend/src/components/UploadRunDialog.tsx b/frontend/src/components/UploadRunDialog.tsx
index c02fdb8..dd609a1 100644
--- a/frontend/src/components/UploadRunDialog.tsx
+++ b/frontend/src/components/UploadRunDialog.tsx
@@ -1,15 +1,15 @@
1import React from 'react'; 1import React from "react";
2import { UploadRunContent } from '@customTypes/Content'; 2import { UploadRunContent } from "@customTypes/Content";
3import { ScoreboardTempUpdate, SourceDemoParser, NetMessages } from '@nekz/sdp'; 3import { ScoreboardTempUpdate, SourceDemoParser, NetMessages } from "@nekz/sdp";
4 4
5import '@css/UploadRunDialog.css'; 5import "@css/UploadRunDialog.css";
6import { Game } from '@customTypes/Game'; 6import { Game } from "@customTypes/Game";
7import { API } from '@api/Api'; 7import { API } from "@api/Api";
8import { useNavigate } from 'react-router-dom'; 8import { useNavigate } from "react-router-dom";
9import useMessage from '@hooks/UseMessage'; 9import useMessage from "@hooks/UseMessage";
10import useConfirm from '@hooks/UseConfirm'; 10import useConfirm from "@hooks/UseConfirm";
11import useMessageLoad from "@hooks/UseMessageLoad"; 11import useMessageLoad from "@hooks/UseMessageLoad";
12import { MapNames } from '@customTypes/MapNames'; 12import { MapNames } from "@customTypes/MapNames";
13 13
14interface UploadRunDialogProps { 14interface UploadRunDialogProps {
15 token?: string; 15 token?: string;
diff --git a/frontend/src/hooks/UseConfirm.tsx b/frontend/src/hooks/UseConfirm.tsx
index e86d70d..593427e 100644
--- a/frontend/src/hooks/UseConfirm.tsx
+++ b/frontend/src/hooks/UseConfirm.tsx
@@ -1,40 +1,40 @@
1import React, { useState } from 'react'; 1import React, { useState } from "react";
2import ConfirmDialog from '@components/ConfirmDialog'; 2import ConfirmDialog from "@components/ConfirmDialog";
3 3
4const useConfirm = () => { 4const useConfirm = () => {
5 const [isOpen, setIsOpen] = useState(false); 5 const [isOpen, setIsOpen] = useState(false);
6 const [title, setTitle] = useState<string>(""); 6 const [title, setTitle] = useState<string>("");
7 const [subtitle, setSubtitle] = useState<string>(""); 7 const [subtitle, setSubtitle] = useState<string>("");
8 const [resolvePromise, setResolvePromise] = useState<((value: boolean) => void) | null>(null); 8 const [resolvePromise, setResolvePromise] = useState<((value: boolean) => void) | null>(null);
9 9
10 const confirm = ( titleN: string, subtitleN: string ) => { 10 const confirm = ( titleN: string, subtitleN: string ) => {
11 setIsOpen(true); 11 setIsOpen(true);
12 setTitle(titleN); 12 setTitle(titleN);
13 setSubtitle(subtitleN); 13 setSubtitle(subtitleN);
14 return new Promise<boolean>((resolve) => { 14 return new Promise<boolean>((resolve) => {
15 setResolvePromise(() => resolve); 15 setResolvePromise(() => resolve);
16 }); 16 });
17 }; 17 };
18 18
19 const handleConfirm = () => { 19 const handleConfirm = () => {
20 setIsOpen(false); 20 setIsOpen(false);
21 if (resolvePromise) { 21 if (resolvePromise) {
22 resolvePromise(true); 22 resolvePromise(true);
23 }
24 } 23 }
24 }
25 25
26 const handleCancel = () => { 26 const handleCancel = () => {
27 setIsOpen(false); 27 setIsOpen(false);
28 if (resolvePromise) { 28 if (resolvePromise) {
29 resolvePromise(false); 29 resolvePromise(false);
30 }
31 } 30 }
31 }
32 32
33 const ConfirmDialogComponent = isOpen && ( 33 const ConfirmDialogComponent = isOpen && (
34 <ConfirmDialog title={title} subtitle={subtitle} onConfirm={handleConfirm} onCancel={handleCancel}></ConfirmDialog> 34 <ConfirmDialog title={title} subtitle={subtitle} onConfirm={handleConfirm} onCancel={handleCancel}></ConfirmDialog>
35 ); 35 );
36 36
37 return { confirm, ConfirmDialogComponent }; 37 return { confirm, ConfirmDialogComponent };
38} 38}
39 39
40export default useConfirm; 40export default useConfirm;
diff --git a/frontend/src/hooks/UseMessage.tsx b/frontend/src/hooks/UseMessage.tsx
index 97ec746..22a5168 100644
--- a/frontend/src/hooks/UseMessage.tsx
+++ b/frontend/src/hooks/UseMessage.tsx
@@ -1,37 +1,37 @@
1import React, { useState } from 'react'; 1import React, { useState } from "react";
2import MessageDialog from "@components/MessageDialog"; 2import MessageDialog from "@components/MessageDialog";
3 3
4const useMessage = () => { 4const useMessage = () => {
5 const [isOpen, setIsOpen] = useState(false); 5 const [isOpen, setIsOpen] = useState(false);
6 6
7 const [title, setTitle] = useState<string>(""); 7 const [title, setTitle] = useState<string>("");
8 const [subtitle, setSubtitle] = useState<string>(""); 8 const [subtitle, setSubtitle] = useState<string>("");
9 const [resolvePromise, setResolvePromise] = useState<(() => void) | null>(null); 9 const [resolvePromise, setResolvePromise] = useState<(() => void) | null>(null);
10 10
11 const message = (title: string, subtitle: string) => { 11 const message = (title: string, subtitle: string) => {
12 setIsOpen(true); 12 setIsOpen(true);
13 setTitle(title); 13 setTitle(title);
14 setSubtitle(subtitle); 14 setSubtitle(subtitle);
15 return new Promise((resolve) => { 15 return new Promise((resolve) => {
16 setResolvePromise(() => resolve); 16 setResolvePromise(() => resolve);
17 }); 17 });
18 }; 18 };
19 19
20 const handleClose = () => { 20 const handleClose = () => {
21 setIsOpen(false); 21 setIsOpen(false);
22 if (resolvePromise) { 22 if (resolvePromise) {
23 resolvePromise(); 23 resolvePromise();
24 setResolvePromise(null); 24 setResolvePromise(null);
25 } 25 }
26 }; 26 };
27 27
28 const MessageDialogComponent = isOpen && ( 28 const MessageDialogComponent = isOpen && (
29 <div className="dialog-container"> 29 <div className="dialog-container">
30 <MessageDialog title={title} subtitle={subtitle} onClose={handleClose}></MessageDialog> 30 <MessageDialog title={title} subtitle={subtitle} onClose={handleClose}></MessageDialog>
31 </div> 31 </div>
32 ); 32 );
33 33
34 return { message, MessageDialogComponent }; 34 return { message, MessageDialogComponent };
35} 35}
36 36
37export default useMessage; 37export default useMessage;
diff --git a/frontend/src/hooks/UseMessageLoad.tsx b/frontend/src/hooks/UseMessageLoad.tsx
index 228c2b4..eb42a45 100644
--- a/frontend/src/hooks/UseMessageLoad.tsx
+++ b/frontend/src/hooks/UseMessageLoad.tsx
@@ -1,35 +1,35 @@
1import React, { useState } from 'react'; 1import React, { useState } from "react";
2import MessageDialogLoad from "@components/MessageDialogLoad"; 2import MessageDialogLoad from "@components/MessageDialogLoad";
3 3
4const useMessageLoad = () => { 4const useMessageLoad = () => {
5 const [isOpen, setIsOpen] = useState(false); 5 const [isOpen, setIsOpen] = useState(false);
6 6
7 const [title, setTitle] = useState<string>(""); 7 const [title, setTitle] = useState<string>("");
8 const [resolvePromise, setResolvePromise] = useState<(() => void) | null>(null); 8 const [resolvePromise, setResolvePromise] = useState<(() => void) | null>(null);
9 9
10 const messageLoad = (title: string) => { 10 const messageLoad = (title: string) => {
11 setIsOpen(true); 11 setIsOpen(true);
12 setTitle(title); 12 setTitle(title);
13 return new Promise((resolve) => { 13 return new Promise((resolve) => {
14 setResolvePromise(() => resolve); 14 setResolvePromise(() => resolve);
15 }); 15 });
16 }; 16 };
17 17
18 const messageLoadClose = () => { 18 const messageLoadClose = () => {
19 setIsOpen(false); 19 setIsOpen(false);
20 if (resolvePromise) { 20 if (resolvePromise) {
21 resolvePromise(); 21 resolvePromise();
22 setResolvePromise(null); 22 setResolvePromise(null);
23 } 23 }
24 }; 24 };
25 25
26 const MessageDialogLoadComponent = isOpen && ( 26 const MessageDialogLoadComponent = isOpen && (
27 <div className="dialog-container"> 27 <div className="dialog-container">
28 <MessageDialogLoad title={title} onClose={messageLoadClose}></MessageDialogLoad> 28 <MessageDialogLoad title={title} onClose={messageLoadClose}></MessageDialogLoad>
29 </div> 29 </div>
30 ); 30 );
31 31
32 return { messageLoad, messageLoadClose, MessageDialogLoadComponent }; 32 return { messageLoad, messageLoadClose, MessageDialogLoadComponent };
33} 33}
34 34
35export default useMessageLoad; 35export default useMessageLoad;
diff --git a/frontend/src/images/Images.tsx b/frontend/src/images/Images.tsx
index 198431b..c4511ef 100644
--- a/frontend/src/images/Images.tsx
+++ b/frontend/src/images/Images.tsx
@@ -1,25 +1,25 @@
1import logo from "./png/logo.png" 1import logo from "./png/logo.png"
2import login from "./png/login.png" 2import login from "./png/login.png"
3import img1 from './png/1.png'; 3import img1 from "./png/1.png";
4import img2 from './png/2.png'; 4import img2 from "./png/2.png";
5import img3 from './png/3.png'; 5import img3 from "./png/3.png";
6import img4 from './png/4.png'; 6import img4 from "./png/4.png";
7import img5 from './png/5.png'; 7import img5 from "./png/5.png";
8import img6 from './png/6.png'; 8import img6 from "./png/6.png";
9import img7 from './png/7.png'; 9import img7 from "./png/7.png";
10import img8 from './png/8.png'; 10import img8 from "./png/8.png";
11import img9 from './png/9.png'; 11import img9 from "./png/9.png";
12import img10 from './png/10.png'; 12import img10 from "./png/10.png";
13import img11 from './png/11.png'; 13import img11 from "./png/11.png";
14import img12 from './png/12.png'; 14import img12 from "./png/12.png";
15import img13 from './png/13.png'; 15import img13 from "./png/13.png";
16import img14 from './png/14.png'; 16import img14 from "./png/14.png";
17import img15 from './png/15.png'; 17import img15 from "./png/15.png";
18import img16 from './png/16.png'; 18import img16 from "./png/16.png";
19import img17 from './png/17.png'; 19import img17 from "./png/17.png";
20import img18 from './png/18.png'; 20import img18 from "./png/18.png";
21import img19 from './png/19.png'; 21import img19 from "./png/19.png";
22import img20 from './png/20.png'; 22import img20 from "./png/20.png";
23import img21 from "./png/21.png"; 23import img21 from "./png/21.png";
24 24
25export const LogoIcon = logo; 25export const LogoIcon = logo;
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index eec2ff4..13d180c 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -1,11 +1,11 @@
1import React from 'react'; 1import React from "react";
2import ReactDOM from 'react-dom/client'; 2import ReactDOM from "react-dom/client";
3import { BrowserRouter } from "react-router-dom"; 3import { BrowserRouter } from "react-router-dom";
4 4
5import App from './App'; 5import App from "./App";
6 6
7const root = ReactDOM.createRoot( 7const root = ReactDOM.createRoot(
8 document.getElementById('root') as HTMLElement 8 document.getElementById("root") as HTMLElement
9); 9);
10 10
11root.render( 11root.render(
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>}
diff --git a/frontend/src/react-app-env.d.ts b/frontend/src/react-app-env.d.ts
deleted file mode 100644
index 8265915..0000000
--- a/frontend/src/react-app-env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1declare module "*.png";
2declare module "*.css";
diff --git a/frontend/src/types/Content.ts b/frontend/src/types/Content.ts
index 775fab4..accb441 100644
--- a/frontend/src/types/Content.ts
+++ b/frontend/src/types/Content.ts
@@ -13,10 +13,6 @@ export interface MapDiscussionContent {
13 content: string; 13 content: string;
14}; 14};
15 15
16export interface MapDiscussionCommentContent {
17 comment: string;
18};
19
20export interface UploadRunContent { 16export interface UploadRunContent {
21 host_demo: File | null; 17 host_demo: File | null;
22 partner_demo: File | null; 18 partner_demo: File | null;
diff --git a/frontend/src/types/Game.ts b/frontend/src/types/Game.ts
index 1a80341..104d02a 100644
--- a/frontend/src/types/Game.ts
+++ b/frontend/src/types/Game.ts
@@ -1,4 +1,4 @@
1import type { Map } from '@customTypes/Map'; 1import type { Map } from "@customTypes/Map";
2 2
3 3
4export interface Game { 4export interface Game {
diff --git a/frontend/src/types/Map.ts b/frontend/src/types/Map.ts
index 4f8eabf..b12d965 100644
--- a/frontend/src/types/Map.ts
+++ b/frontend/src/types/Map.ts
@@ -1,6 +1,6 @@
1import type { Category, GameCategoryPortals } from '@customTypes/Game'; 1import type { Category, GameCategoryPortals } from "@customTypes/Game";
2import type { Pagination } from '@customTypes/Pagination'; 2import type { Pagination } from "@customTypes/Pagination";
3import type { UserShort } from '@customTypes/Profile'; 3import type { UserShort } from "@customTypes/Profile";
4 4
5export interface Map { 5export interface Map {
6 id: number; 6 id: number;
diff --git a/frontend/src/types/MapNames.ts b/frontend/src/types/MapNames.ts
index b6313e7..7a408a3 100644
--- a/frontend/src/types/MapNames.ts
+++ b/frontend/src/types/MapNames.ts
@@ -1,127 +1,127 @@
1export const MapNames: { [key: string]: number } = { 1export const MapNames: { [key: string]: number } = {
2 "sp_a1_intro1": 1, 2 "sp_a1_intro1": 1,
3 "sp_a1_intro2": 2, 3 "sp_a1_intro2": 2,
4 "sp_a1_intro3": 3, 4 "sp_a1_intro3": 3,
5 "sp_a1_intro4": 4, 5 "sp_a1_intro4": 4,
6 "sp_a1_intro5": 5, 6 "sp_a1_intro5": 5,
7 "sp_a1_intro6": 6, 7 "sp_a1_intro6": 6,
8 "sp_a1_intro7": 7, 8 "sp_a1_intro7": 7,
9 "sp_a1_wakeup": 8, 9 "sp_a1_wakeup": 8,
10 "sp_a2_intro": 9, 10 "sp_a2_intro": 9,
11 11
12 "sp_a2_laser_intro": 10, 12 "sp_a2_laser_intro": 10,
13 "sp_a2_laser_stairs": 11, 13 "sp_a2_laser_stairs": 11,
14 "sp_a2_dual_lasers": 12, 14 "sp_a2_dual_lasers": 12,
15 "sp_a2_laser_over_goo": 13, 15 "sp_a2_laser_over_goo": 13,
16 "sp_a2_catapult_intro": 14, 16 "sp_a2_catapult_intro": 14,
17 "sp_a2_trust_fling": 15, 17 "sp_a2_trust_fling": 15,
18 "sp_a2_pit_flings": 16, 18 "sp_a2_pit_flings": 16,
19 "sp_a2_fizzler_intro": 17, 19 "sp_a2_fizzler_intro": 17,
20 20
21 "sp_a2_sphere_peek": 18, 21 "sp_a2_sphere_peek": 18,
22 "sp_a2_ricochet": 19, 22 "sp_a2_ricochet": 19,
23 "sp_a2_bridge_intro": 20, 23 "sp_a2_bridge_intro": 20,
24 "sp_a2_bridge_the_gap": 21, 24 "sp_a2_bridge_the_gap": 21,
25 "sp_a2_turret_intro": 22, 25 "sp_a2_turret_intro": 22,
26 "sp_a2_laser_relays": 23, 26 "sp_a2_laser_relays": 23,
27 "sp_a2_turret_blocker": 24, 27 "sp_a2_turret_blocker": 24,
28 "sp_a2_laser_vs_turret": 25, 28 "sp_a2_laser_vs_turret": 25,
29 "sp_a2_pull_the_rug": 26, 29 "sp_a2_pull_the_rug": 26,
30 30
31 "sp_a2_column_blocker": 27, 31 "sp_a2_column_blocker": 27,
32 "sp_a2_laser_chaining": 28, 32 "sp_a2_laser_chaining": 28,
33 "sp_a2_triple_laser": 29, 33 "sp_a2_triple_laser": 29,
34 "sp_a2_bts1": 30, 34 "sp_a2_bts1": 30,
35 "sp_a2_bts2": 31, 35 "sp_a2_bts2": 31,
36 36
37 "sp_a2_bts3": 32, 37 "sp_a2_bts3": 32,
38 "sp_a2_bts4": 33, 38 "sp_a2_bts4": 33,
39 "sp_a2_bts5": 34, 39 "sp_a2_bts5": 34,
40 "sp_a2_core": 35, 40 "sp_a2_core": 35,
41 41
42 "sp_a3_01": 36, 42 "sp_a3_01": 36,
43 "sp_a3_03": 37, 43 "sp_a3_03": 37,
44 "sp_a3_jump_intro": 38, 44 "sp_a3_jump_intro": 38,
45 "sp_a3_bomb_flings": 39, 45 "sp_a3_bomb_flings": 39,
46 "sp_a3_crazy_box": 40, 46 "sp_a3_crazy_box": 40,
47 "sp_a3_transition01": 41, 47 "sp_a3_transition01": 41,
48 48
49 "sp_a3_speed_ramp": 42, 49 "sp_a3_speed_ramp": 42,
50 "sp_a3_speed_flings": 43, 50 "sp_a3_speed_flings": 43,
51 "sp_a3_portal_intro": 44, 51 "sp_a3_portal_intro": 44,
52 "sp_a3_end": 45, 52 "sp_a3_end": 45,
53 53
54 "sp_a4_intro": 46, 54 "sp_a4_intro": 46,
55 "sp_a4_tb_intro": 47, 55 "sp_a4_tb_intro": 47,
56 "sp_a4_tb_trust_drop": 48, 56 "sp_a4_tb_trust_drop": 48,
57 "sp_a4_tb_wall_button": 49, 57 "sp_a4_tb_wall_button": 49,
58 "sp_a4_tb_polarity": 50, 58 "sp_a4_tb_polarity": 50,
59 "sp_a4_tb_catch": 51, 59 "sp_a4_tb_catch": 51,
60 "sp_a4_stop_the_box": 52, 60 "sp_a4_stop_the_box": 52,
61 "sp_a4_laser_catapult": 53, 61 "sp_a4_laser_catapult": 53,
62 "sp_a4_laser_platform": 54, 62 "sp_a4_laser_platform": 54,
63 "sp_a4_speed_tb_catch": 55, 63 "sp_a4_speed_tb_catch": 55,
64 "sp_a4_jump_polarity": 56, 64 "sp_a4_jump_polarity": 56,
65 65
66 "sp_a4_finale1": 57, 66 "sp_a4_finale1": 57,
67 "sp_a4_finale2": 58, 67 "sp_a4_finale2": 58,
68 "sp_a4_finale3": 59, 68 "sp_a4_finale3": 59,
69 "sp_a4_finale4": 60, 69 "sp_a4_finale4": 60,
70 70
71 "mp_coop_start": 61, 71 "mp_coop_start": 61,
72 "mp_coop_lobby_3": 62, 72 "mp_coop_lobby_3": 62,
73 73
74 "mp_coop_doors": 63, 74 "mp_coop_doors": 63,
75 "mp_coop_race_2": 64, 75 "mp_coop_race_2": 64,
76 "mp_coop_laser_2": 65, 76 "mp_coop_laser_2": 65,
77 "mp_coop_rat_maze": 66, 77 "mp_coop_rat_maze": 66,
78 "mp_coop_laser_crusher": 67, 78 "mp_coop_laser_crusher": 67,
79 "mp_coop_teambts": 68, 79 "mp_coop_teambts": 68,
80 80
81 "mp_coop_fling_3": 69, 81 "mp_coop_fling_3": 69,
82 "mp_coop_infinifling_train": 70, 82 "mp_coop_infinifling_train": 70,
83 "mp_coop_come_along": 71, 83 "mp_coop_come_along": 71,
84 "mp_coop_fling_1": 72, 84 "mp_coop_fling_1": 72,
85 "mp_coop_catapult_1": 73, 85 "mp_coop_catapult_1": 73,
86 "mp_coop_multifling_1": 74, 86 "mp_coop_multifling_1": 74,
87 "mp_coop_fling_crushers": 75, 87 "mp_coop_fling_crushers": 75,
88 "mp_coop_fan": 76, 88 "mp_coop_fan": 76,
89 89
90 "mp_coop_wall_intro": 77, 90 "mp_coop_wall_intro": 77,
91 "mp_coop_wall_2": 78, 91 "mp_coop_wall_2": 78,
92 "mp_coop_catapult_wall_intro": 79, 92 "mp_coop_catapult_wall_intro": 79,
93 "mp_coop_wall_block": 80, 93 "mp_coop_wall_block": 80,
94 "mp_coop_catapult_2": 81, 94 "mp_coop_catapult_2": 81,
95 "mp_coop_turret_walls": 82, 95 "mp_coop_turret_walls": 82,
96 "mp_coop_turret_ball": 83, 96 "mp_coop_turret_ball": 83,
97 "mp_coop_wall_5": 84, 97 "mp_coop_wall_5": 84,
98 98
99 "mp_coop_tbeam_redirect": 85, 99 "mp_coop_tbeam_redirect": 85,
100 "mp_coop_tbeam_drill": 86, 100 "mp_coop_tbeam_drill": 86,
101 "mp_coop_tbeam_catch_grind_1": 87, 101 "mp_coop_tbeam_catch_grind_1": 87,
102 "mp_coop_tbeam_laser_1": 88, 102 "mp_coop_tbeam_laser_1": 88,
103 "mp_coop_tbeam_polarity": 89, 103 "mp_coop_tbeam_polarity": 89,
104 "mp_coop_tbeam_polarity2": 90, 104 "mp_coop_tbeam_polarity2": 90,
105 "mp_coop_tbeam_polarity3": 91, 105 "mp_coop_tbeam_polarity3": 91,
106 "mp_coop_tbeam_maze": 92, 106 "mp_coop_tbeam_maze": 92,
107 "mp_coop_tbeam_end": 93, 107 "mp_coop_tbeam_end": 93,
108 108
109 "mp_coop_paint_come_along": 94, 109 "mp_coop_paint_come_along": 94,
110 "mp_coop_paint_redirect": 95, 110 "mp_coop_paint_redirect": 95,
111 "mp_coop_paint_bridge": 96, 111 "mp_coop_paint_bridge": 96,
112 "mp_coop_paint_walljumps": 97, 112 "mp_coop_paint_walljumps": 97,
113 "mp_coop_paint_speed_fling": 98, 113 "mp_coop_paint_speed_fling": 98,
114 "mp_coop_paint_red_racer": 99, 114 "mp_coop_paint_red_racer": 99,
115 "mp_coop_paint_speed_catch": 100, 115 "mp_coop_paint_speed_catch": 100,
116 "mp_coop_paint_longjump_intro": 101, 116 "mp_coop_paint_longjump_intro": 101,
117 117
118 "mp_coop_separation_1": 102, 118 "mp_coop_separation_1": 102,
119 "mp_coop_tripleaxis": 103, 119 "mp_coop_tripleaxis": 103,
120 "mp_coop_catapult_catch": 104, 120 "mp_coop_catapult_catch": 104,
121 "mp_coop_2paints_1bridge": 105, 121 "mp_coop_2paints_1bridge": 105,
122 "mp_coop_paint_conversion": 106, 122 "mp_coop_paint_conversion": 106,
123 "mp_coop_bridge_catch": 107, 123 "mp_coop_bridge_catch": 107,
124 "mp_coop_laser_tbeam": 108, 124 "mp_coop_laser_tbeam": 108,
125 "mp_coop_paint_rat_maze": 109, 125 "mp_coop_paint_rat_maze": 109,
126 "mp_coop_paint_crazy_box": 110, 126 "mp_coop_paint_crazy_box": 110,
127}; 127};
diff --git a/frontend/src/utils/Jwt.ts b/frontend/src/utils/Jwt.ts
index ce351fb..a6d1866 100644
--- a/frontend/src/utils/Jwt.ts
+++ b/frontend/src/utils/Jwt.ts
@@ -3,20 +3,20 @@ export function get_user_id_from_token(token: string | undefined): string | unde
3 if (!token) { 3 if (!token) {
4 return undefined; 4 return undefined;
5 } 5 }
6 const parts = token.split('.'); 6 const parts = token.split(".");
7 if (parts.length !== 3) { 7 if (parts.length !== 3) {
8 return undefined; 8 return undefined;
9 } 9 }
10 const base64Url = parts[1]; 10 const base64Url = parts[1];
11 const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); 11 const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
12 12
13 const jsonPayload = decodeURIComponent( 13 const jsonPayload = decodeURIComponent(
14 atob(base64) 14 atob(base64)
15 .split('') 15 .split("")
16 .map(function (c) { 16 .map(function (c) {
17 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 17 return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
18 }) 18 })
19 .join('') 19 .join("")
20 ); 20 );
21 return JSON.parse(jsonPayload).sub; 21 return JSON.parse(jsonPayload).sub;
22}; 22};
@@ -25,20 +25,20 @@ export function get_user_mod_from_token(token: string | undefined): boolean | un
25 if (!token) { 25 if (!token) {
26 return undefined; 26 return undefined;
27 } 27 }
28 const parts = token.split('.'); 28 const parts = token.split(".");
29 if (parts.length !== 3) { 29 if (parts.length !== 3) {
30 return undefined; 30 return undefined;
31 } 31 }
32 const base64Url = parts[1]; 32 const base64Url = parts[1];
33 const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); 33 const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
34 34
35 const jsonPayload = decodeURIComponent( 35 const jsonPayload = decodeURIComponent(
36 atob(base64) 36 atob(base64)
37 .split('') 37 .split("")
38 .map(function (c) { 38 .map(function (c) {
39 return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); 39 return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
40 }) 40 })
41 .join('') 41 .join("")
42 ); 42 );
43 return JSON.parse(jsonPayload).mod; 43 return JSON.parse(jsonPayload).mod;
44}; 44};
diff --git a/frontend/src/utils/Time.ts b/frontend/src/utils/Time.ts
index b83a7ed..9cbe793 100644
--- a/frontend/src/utils/Time.ts
+++ b/frontend/src/utils/Time.ts
@@ -5,38 +5,38 @@ export function time_ago(date: any) {
5 const seconds = Math.floor((now - localDate.getTime()) / 1000); 5 const seconds = Math.floor((now - localDate.getTime()) / 1000);
6 6
7 let interval = Math.floor(seconds / 31536000); 7 let interval = Math.floor(seconds / 31536000);
8 if (interval === 1) {return interval + ' year ago';} 8 if (interval === 1) {return interval + " year ago";}
9 if (interval > 1) {return interval + ' years ago';} 9 if (interval > 1) {return interval + " years ago";}
10 10
11 interval = Math.floor(seconds / 2592000); 11 interval = Math.floor(seconds / 2592000);
12 if (interval === 1) {return interval + ' month ago';} 12 if (interval === 1) {return interval + " month ago";}
13 if (interval > 1) {return interval + ' months ago';} 13 if (interval > 1) {return interval + " months ago";}
14 14
15 interval = Math.floor(seconds / 86400); 15 interval = Math.floor(seconds / 86400);
16 if (interval === 1) {return interval + ' day ago';} 16 if (interval === 1) {return interval + " day ago";}
17 if (interval > 1) {return interval + ' days ago';} 17 if (interval > 1) {return interval + " days ago";}
18 18
19 interval = Math.floor(seconds / 3600); 19 interval = Math.floor(seconds / 3600);
20 if (interval === 1) {return interval + ' hour ago';} 20 if (interval === 1) {return interval + " hour ago";}
21 if (interval > 1) {return interval + ' hours ago';} 21 if (interval > 1) {return interval + " hours ago";}
22 22
23 interval = Math.floor(seconds / 60); 23 interval = Math.floor(seconds / 60);
24 if (interval === 1) {return interval + ' minute ago';} 24 if (interval === 1) {return interval + " minute ago";}
25 if (interval > 1) {return interval + ' minutes ago';} 25 if (interval > 1) {return interval + " minutes ago";}
26 26
27 if(seconds < 10) return 'just now'; 27 if(seconds < 10) return "just now";
28 28
29 return Math.floor(seconds) + ' seconds ago'; 29 return Math.floor(seconds) + " seconds ago";
30}; 30};
31 31
32export function ticks_to_time(ticks: number) { 32export function ticks_to_time(ticks: number) {
33 let seconds = Math.floor(ticks / 60) 33 let seconds = Math.floor(ticks / 60)
34 let minutes = Math.floor(seconds / 60) 34 let minutes = Math.floor(seconds / 60)
35 let hours = Math.floor(minutes / 60) 35 const hours = Math.floor(minutes / 60)
36 36
37 let milliseconds = Math.floor((ticks % 60) * 1000 / 60) 37 const milliseconds = Math.floor((ticks % 60) * 1000 / 60)
38 seconds = seconds % 60; 38 seconds = seconds % 60;
39 minutes = minutes % 60; 39 minutes = minutes % 60;
40 40
41 return `${hours === 0 ? "" : hours + ":"}${minutes === 0 ? "" : hours > 0 ? minutes.toString().padStart(2, '0') + ":" : (minutes + ":")}${minutes > 0 ? seconds.toString().padStart(2, '0') : seconds}.${milliseconds.toString().padStart(3, '0')}`; 41 return `${hours === 0 ? "" : hours + ":"}${minutes === 0 ? "" : hours > 0 ? minutes.toString().padStart(2, "0") + ":" : (minutes + ":")}${minutes > 0 ? seconds.toString().padStart(2, "0") : seconds}.${milliseconds.toString().padStart(3, "0")}`;
42}; \ No newline at end of file 42}; \ No newline at end of file