aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/pages')
-rw-r--r--frontend/src/pages/Homepage.tsx2
-rw-r--r--frontend/src/pages/Maplist.tsx5
-rw-r--r--frontend/src/pages/Maps.tsx14
-rw-r--r--frontend/src/pages/Profile.tsx131
-rw-r--r--frontend/src/pages/Rankings.tsx65
-rw-r--r--frontend/src/pages/Rules.tsx4
-rw-r--r--frontend/src/pages/User.tsx146
7 files changed, 153 insertions, 214 deletions
diff --git a/frontend/src/pages/Homepage.tsx b/frontend/src/pages/Homepage.tsx
index 8e5bb4b..8c1cd48 100644
--- a/frontend/src/pages/Homepage.tsx
+++ b/frontend/src/pages/Homepage.tsx
@@ -7,7 +7,7 @@ const Homepage: React.FC = () => {
7 <main> 7 <main>
8 <section> 8 <section>
9 <p/> 9 <p/>
10 <h1><img src={PortalIcon} alt="lphub"/>Welcome to Least Portals Hub!</h1> 10 <h1>Welcome to Least Portals Hub!</h1>
11 <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> 11 <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>
12 <p>The website should feel intuitive to navigate around. For any type of feedback, reach us at LPHUB Discord server.</p> 12 <p>The website should feel intuitive to navigate around. For any type of feedback, reach us at LPHUB Discord server.</p>
13 <p>By using LPHUB, you agree that you have read the 'Leaderboard Rules' and the 'About LPHUB' pages.</p> 13 <p>By using LPHUB, you agree that you have read the 'Leaderboard Rules' and the 'About LPHUB' pages.</p>
diff --git a/frontend/src/pages/Maplist.tsx b/frontend/src/pages/Maplist.tsx
index 5d0c852..9526d18 100644
--- a/frontend/src/pages/Maplist.tsx
+++ b/frontend/src/pages/Maplist.tsx
@@ -64,6 +64,7 @@ const Maplist: React.FC = () => {
64 // console.log(foundGame) 64 // console.log(foundGame)
65 if (foundGame) { 65 if (foundGame) {
66 setGame(foundGame); 66 setGame(foundGame);
67 setLoad(false);
67 } 68 }
68 }; 69 };
69 70
@@ -73,9 +74,9 @@ const Maplist: React.FC = () => {
73 setNumChapters(games_chapters.chapters.length); 74 setNumChapters(games_chapters.chapters.length);
74 } 75 }
75 76
77 setLoad(true);
76 _fetch_game(); 78 _fetch_game();
77 _fetch_game_chapters(); 79 _fetch_game_chapters();
78 setLoad(true);
79 }, []); 80 }, []);
80 81
81 useEffect(() => { 82 useEffect(() => {
@@ -96,7 +97,7 @@ const Maplist: React.FC = () => {
96 </button> 97 </button>
97 </Link> 98 </Link>
98 </section> 99 </section>
99 {!load ? ( 100 {load ? (
100 <div></div> 101 <div></div>
101 ) : ( 102 ) : (
102 <section> 103 <section>
diff --git a/frontend/src/pages/Maps.tsx b/frontend/src/pages/Maps.tsx
index 62fc3cc..13ee4d9 100644
--- a/frontend/src/pages/Maps.tsx
+++ b/frontend/src/pages/Maps.tsx
@@ -7,18 +7,16 @@ import Leaderboards from '../components/Leaderboards';
7import Discussions from '../components/Discussions'; 7import Discussions from '../components/Discussions';
8import ModMenu from '../components/ModMenu'; 8import ModMenu from '../components/ModMenu';
9import { MapDiscussions, MapLeaderboard, MapSummary } from '../types/Map'; 9import { MapDiscussions, MapLeaderboard, MapSummary } from '../types/Map';
10import { UserProfile } from '../types/Profile';
11import { API } from '../api/Api'; 10import { API } from '../api/Api';
12import "../css/Maps.css"; 11import "../css/Maps.css";
13import Loading from '../components/Loading'; 12import Loading from '../components/Loading';
14 13
15interface MapProps { 14interface MapProps {
16 profile?: UserProfile; 15 token?: string;
17 isModerator: boolean; 16 isModerator: boolean;
18 onUploadRun: (mapID: number) => void;
19}; 17};
20 18
21const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => { 19const Maps: React.FC<MapProps> = ({ token, isModerator }) => {
22 20
23 const [selectedRun, setSelectedRun] = React.useState<number>(0); 21 const [selectedRun, setSelectedRun] = React.useState<number>(0);
24 22
@@ -39,7 +37,8 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => {
39 37
40 const _fetch_map_leaderboards = async () => { 38 const _fetch_map_leaderboards = async () => {
41 const mapLeaderboards = await API.get_map_leaderboard(mapID); 39 const mapLeaderboards = await API.get_map_leaderboard(mapID);
42 console.log(mapLeaderboards?.records[0]); 40 console.log("lbs:")
41 console.log(mapLeaderboards);
43 setMapLeaderboardData(mapLeaderboards); 42 setMapLeaderboardData(mapLeaderboards);
44 }; 43 };
45 44
@@ -77,7 +76,7 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => {
77 76
78 return ( 77 return (
79 <> 78 <>
80 {isModerator && <ModMenu data={mapSummaryData} selectedRun={selectedRun} mapID={mapID} />} 79 {isModerator && <ModMenu token={token} data={mapSummaryData} selectedRun={selectedRun} mapID={mapID} />}
81 80
82 <div id='background-image'> 81 <div id='background-image'>
83 <img src={mapSummaryData.map.image} alt="" /> 82 <img src={mapSummaryData.map.image} alt="" />
@@ -88,7 +87,6 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => {
88 <Link to="/games"><button className='nav-button' style={{ borderRadius: "20px 0px 0px 20px" }}><i className='triangle'></i><span>Games List</span></button></Link> 87 <Link to="/games"><button className='nav-button' style={{ borderRadius: "20px 0px 0px 20px" }}><i className='triangle'></i><span>Games List</span></button></Link>
89 <Link to={`/games/${!mapSummaryData.map.is_coop ? "1" : "2"}?chapter=${mapSummaryData.map.chapter_name.split(" ")[1]}`}><button className='nav-button' style={{ borderRadius: "0px 20px 20px 0px", marginLeft: "2px" }}><i className='triangle'></i><span>{mapSummaryData.map.chapter_name}</span></button></Link> 88 <Link to={`/games/${!mapSummaryData.map.is_coop ? "1" : "2"}?chapter=${mapSummaryData.map.chapter_name.split(" ")[1]}`}><button className='nav-button' style={{ borderRadius: "0px 20px 20px 0px", marginLeft: "2px" }}><i className='triangle'></i><span>{mapSummaryData.map.chapter_name}</span></button></Link>
90 <br /><span><b>{mapSummaryData.map.map_name}</b></span> 89 <br /><span><b>{mapSummaryData.map.map_name}</b></span>
91 {profile && <button onClick={() => onUploadRun(mapSummaryData.map.id)}>Submit a Run</button>}
92 </div> 90 </div>
93 </section> 91 </section>
94 92
@@ -100,7 +98,7 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => {
100 98
101 {navState === 0 && <Summary selectedRun={selectedRun} setSelectedRun={setSelectedRun} data={mapSummaryData} />} 99 {navState === 0 && <Summary selectedRun={selectedRun} setSelectedRun={setSelectedRun} data={mapSummaryData} />}
102 {navState === 1 && <Leaderboards data={mapLeaderboardData} />} 100 {navState === 1 && <Leaderboards data={mapLeaderboardData} />}
103 {navState === 2 && <Discussions data={mapDiscussionsData} isModerator={isModerator} mapID={mapID} onRefresh={() => _fetch_map_discussions()} />} 101 {navState === 2 && <Discussions data={mapDiscussionsData} token={token} isModerator={isModerator} mapID={mapID} onRefresh={() => _fetch_map_discussions()} />}
104 </main> 102 </main>
105 </> 103 </>
106 ); 104 );
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 8eec23c..e20d930 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -1,5 +1,5 @@
1import React from 'react'; 1import React from 'react';
2import { useLocation, useNavigate } from 'react-router-dom'; 2import { Link, useLocation, useNavigate } from 'react-router-dom';
3 3
4import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; 4import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images';
5import { UserProfile } from '../types/Profile'; 5import { UserProfile } from '../types/Profile';
@@ -7,104 +7,74 @@ import { Game, GameChapters } from '../types/Game';
7import { Map } from '../types/Map'; 7import { Map } from '../types/Map';
8import { ticks_to_time } from '../utils/Time'; 8import { ticks_to_time } from '../utils/Time';
9import "../css/Profile.css"; 9import "../css/Profile.css";
10import { API } from '../api/Api';
10 11
11interface ProfileProps { 12interface ProfileProps {
12 profile?: UserProfile; 13 profile?: UserProfile;
14 token?: string;
15 gameData: Game[];
13} 16}
14 17
15const Profile: React.FC<ProfileProps> = ({ profile }) => { 18const Profile: React.FC<ProfileProps> = ({ profile, token, gameData }) => {
16
17
18 const location = useLocation();
19 const navigate = useNavigate();
20
21 React.useEffect(() => {
22 if (!profile) {
23 navigate("/");
24 };
25 }, [profile]);
26 19
27 const [navState, setNavState] = React.useState(0); 20 const [navState, setNavState] = React.useState(0);
28 const [pageNumber, setPageNumber] = React.useState(1); 21 const [pageNumber, setPageNumber] = React.useState(1);
29 const [pageMax, setPageMax] = React.useState(0); 22 const [pageMax, setPageMax] = React.useState(0);
30 23
31 const [game, setGame] = React.useState("0") 24 const [game, setGame] = React.useState("0")
32 const [gameData, setGameData] = React.useState<Game[]>([]);
33 const [chapter, setChapter] = React.useState("0") 25 const [chapter, setChapter] = React.useState("0")
34 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); 26 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null);
35 const [maps, setMaps] = React.useState<Map[]>([]); 27 const [maps, setMaps] = React.useState<Map[]>([]);
36 28
37 function NavClick() { 29 const navigate = useNavigate();
38 if (profile) {
39 const btn = document.querySelectorAll("#section2 button");
40 btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" });
41 (btn[navState] as HTMLElement).style.backgroundColor = "#202232";
42 30
43 document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "") 31 const _update_profile = () => {
44 if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); } 32 if (token) {
45 if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); } 33 API.post_profile(token).then(() => navigate(0));
46 } 34 }
47 } 35 };
48 36
49 function UpdateProfile() { 37 const _get_game_chapters = async () => {
50 fetch(`https://lp.ardapektezol.com/api/v1/profile`, { 38 if (game && game !== "0") {
51 method: 'POST', 39 const gameChapters = await API.get_games_chapters(game);
52 headers: { Authorization: "" } 40 setChapterData(gameChapters);
53 }).then(r => r.json()) 41 } else if (game && game === "0") {
54 .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`)) 42 setPageMax(Math.ceil(profile!.records.length / 20));
55 } 43 setPageNumber(1);
44 }
45 };
56 46
57 React.useEffect(() => { 47 const _get_game_maps = async () => {
58 if (profile) { 48 if (chapter === "0") {
59 fetch("https://lp.ardapektezol.com/api/v1/games") 49 const gameMaps = await API.get_game_maps(game);
60 .then(r => r.json()) 50 setMaps(gameMaps);
61 .then(d => { 51 setPageMax(Math.ceil(gameMaps.length / 20));
62 setGameData(d.data) 52 setPageNumber(1);
63 setGame("0") 53 } else {
64 }) 54 const gameChapters = await API.get_chapters(chapter);
55 setMaps(gameChapters.maps);
56 setPageMax(Math.ceil(gameChapters.maps.length / 20));
57 setPageNumber(1);
65 } 58 }
66 }, [profile, location]); 59 };
60
61 React.useEffect(() => {
62 if (!profile) {
63 navigate("/");
64 };
65 }, [profile]);
67 66
68 React.useEffect(() => { 67 React.useEffect(() => {
69 if (profile) { 68 if (profile) {
70 if (game && game !== "0") { 69 _get_game_chapters();
71 fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`)
72 .then(r => r.json())
73 .then(d => {
74 setChapterData(d.data)
75 setChapter("0");
76 // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0"
77 })
78
79 } else if (game && game === "0") {
80 setPageMax(Math.ceil(profile.records.length / 20))
81 setPageNumber(1)
82 }
83 } 70 }
84 }, [profile, game, location]); 71 }, [profile, game]);
85 72
86 React.useEffect(() => { 73 React.useEffect(() => {
87 if (game !== "0") { 74 if (profile && game !== "0") {
88 if (chapter === "0") { 75 _get_game_maps();
89 fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`)
90 .then(r => r.json())
91 .then(d => {
92 setMaps(d.data.maps);
93 setPageMax(Math.ceil(d.data.maps.length / 20))
94 setPageNumber(1)
95 })
96 } else {
97 fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`)
98 .then(r => r.json())
99 .then(d => {
100 setMaps(d.data.maps);
101 setPageMax(Math.ceil(d.data.maps.length / 20))
102 setPageNumber(1)
103 })
104
105 }
106 } 76 }
107 }, [game, chapter, chapterData]) 77 }, [profile, game, chapter, chapterData])
108 78
109 if (!profile) { 79 if (!profile) {
110 return ( 80 return (
@@ -118,7 +88,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => {
118 88
119 {profile.profile 89 {profile.profile
120 ? ( 90 ? (
121 <div id='profile-image' onClick={() => UpdateProfile()}> 91 <div id='profile-image' onClick={_update_profile}>
122 <img src={profile.avatar_link} alt="profile-image"></img> 92 <img src={profile.avatar_link} alt="profile-image"></img>
123 <span>Refresh</span> 93 <span>Refresh</span>
124 </div> 94 </div>
@@ -187,7 +157,14 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => {
187 {gameData === null ? <select>error</select> : 157 {gameData === null ? <select>error</select> :
188 158
189 <select id='select-game' 159 <select id='select-game'
190 onChange={() => setGame((document.querySelector('#select-game') as HTMLInputElement).value)}> 160 onChange={() => {
161 setGame((document.querySelector('#select-game') as HTMLInputElement).value);
162 setChapter("0");
163 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement;
164 if (chapterSelect) {
165 chapterSelect.value = "0";
166 }
167 }}>
191 <option value={0} key={0}>All Scores</option> 168 <option value={0} key={0}>All Scores</option>
192 {gameData.map((e, i) => ( 169 {gameData.map((e, i) => (
193 <option value={e.id} key={i + 1}>{e.name}</option> 170 <option value={e.id} key={i + 1}>{e.name}</option>
@@ -240,7 +217,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => {
240 {r.scores.map((e, i) => (<> 217 {r.scores.map((e, i) => (<>
241 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 218 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
242 219
243 <span>{r.map_name}</span> 220 <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link>
244 221
245 <span style={{ display: "grid" }}>{e.score_count}</span> 222 <span style={{ display: "grid" }}>{e.score_count}</span>
246 223
@@ -252,7 +229,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => {
252 <span style={{ flexDirection: "row-reverse" }}> 229 <span style={{ flexDirection: "row-reverse" }}>
253 230
254 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 231 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
255 <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 232 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
256 {i === 0 && r.scores.length > 1 ? <button onClick={() => { 233 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
257 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 234 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
258 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 235 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
@@ -297,7 +274,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => {
297 <span style={{ flexDirection: "row-reverse" }}> 274 <span style={{ flexDirection: "row-reverse" }}>
298 275
299 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 276 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
300 <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 277 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
301 {i === 0 && record!.scores.length > 1 ? <button onClick={() => { 278 {i === 0 && record!.scores.length > 1 ? <button onClick={() => {
302 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 279 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
303 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 280 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
diff --git a/frontend/src/pages/Rankings.tsx b/frontend/src/pages/Rankings.tsx
index 6dbf3d3..9280b02 100644
--- a/frontend/src/pages/Rankings.tsx
+++ b/frontend/src/pages/Rankings.tsx
@@ -22,14 +22,8 @@ const Rankings: React.FC = () => {
22 const [currentLeaderboardType, setCurrentLeaderboardType] = React.useState<RankingCategories>(RankingCategories.rankings_singleplayer); 22 const [currentLeaderboardType, setCurrentLeaderboardType] = React.useState<RankingCategories>(RankingCategories.rankings_singleplayer);
23 const [load, setLoad] = React.useState<boolean>(false); 23 const [load, setLoad] = React.useState<boolean>(false);
24 24
25 interface ResponseSTUPID {
26 success: boolean;
27 message: string;
28 data: SteamRanking;
29 }
30
31 const _fetch_rankings = async () => { 25 const _fetch_rankings = async () => {
32 const rankings = await API.get_rankings(); 26 const rankings = await API.get_official_rankings();
33 setLeaderboardData(rankings); 27 setLeaderboardData(rankings);
34 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { 28 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) {
35 setCurrentLeaderboard(rankings.rankings_singleplayer) 29 setCurrentLeaderboard(rankings.rankings_singleplayer)
@@ -43,20 +37,15 @@ const Rankings: React.FC = () => {
43 37
44 const __dev_fetch_unofficial_rankings = async () => { 38 const __dev_fetch_unofficial_rankings = async () => {
45 try { 39 try {
46 const response = await fetch("/response.json"); 40 const rankings = await API.get_unofficial_rankings();
47 const result: ResponseSTUPID = await response.json(); 41 setLeaderboardData(rankings);
48 42 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) {
49 if (result.success) { 43 // console.log(_sort_rankings_steam(unofficialRanking.rankings_singleplayer))
50 const unofficialRanking: SteamRanking = result.data; 44 setCurrentLeaderboard(rankings.rankings_singleplayer)
51 setLeaderboardData(unofficialRanking); 45 } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) {
52 if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { 46 setCurrentLeaderboard(rankings.rankings_multiplayer)
53 // console.log(_sort_rankings_steam(unofficialRanking.rankings_singleplayer)) 47 } else {
54 setCurrentLeaderboard(unofficialRanking.rankings_singleplayer) 48 setCurrentLeaderboard(rankings.rankings_overall)
55 } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) {
56 setCurrentLeaderboard(unofficialRanking.rankings_multiplayer)
57 } else {
58 setCurrentLeaderboard(unofficialRanking.rankings_overall)
59 }
60 } 49 }
61 } catch (e) { 50 } catch (e) {
62 console.log(e) 51 console.log(e)
@@ -79,7 +68,7 @@ const Rankings: React.FC = () => {
79 if (leaderboard_type == LeaderboardTypes.official) { 68 if (leaderboard_type == LeaderboardTypes.official) {
80 _fetch_rankings(); 69 _fetch_rankings();
81 } else { 70 } else {
82 71
83 } 72 }
84 } 73 }
85 74
@@ -117,23 +106,23 @@ const Rankings: React.FC = () => {
117 </section> 106 </section>
118 107
119 {load ? 108 {load ?
120 <section className="rankings-leaderboard"> 109 <section className="rankings-leaderboard">
121 <div className="ranks-container"> 110 <div className="ranks-container">
122 <div className="leaderboard-entry header"> 111 <div className="leaderboard-entry header">
123 <span>Rank</span> 112 <span>Rank</span>
124 <span>Player</span> 113 <span>Player</span>
125 <span>Portals</span> 114 <span>Portals</span>
115 </div>
116
117 <div className="splitter"></div>
118
119 {currentLeaderboard?.map((curRankingData, i) => {
120 return <RankingEntry currentLeaderboardType={currentLeaderboardType} curRankingData={curRankingData} key={i}></RankingEntry>
121 })
122 }
126 </div> 123 </div>
127 124 </section>
128 <div className="splitter"></div> 125 : null}
129
130 {currentLeaderboard?.map((curRankingData, i) => {
131 return <RankingEntry currentLeaderboardType={currentLeaderboardType} curRankingData={curRankingData} key={i}></RankingEntry>
132 })
133 }
134 </div>
135 </section>
136 : null}
137 </main> 126 </main>
138 ) 127 )
139} 128}
diff --git a/frontend/src/pages/Rules.tsx b/frontend/src/pages/Rules.tsx
index 516b73c..99fff1f 100644
--- a/frontend/src/pages/Rules.tsx
+++ b/frontend/src/pages/Rules.tsx
@@ -11,7 +11,7 @@ const Rules: React.FC = () => {
11 const fetchRules = async () => { 11 const fetchRules = async () => {
12 try { 12 try {
13 const response = await fetch( 13 const response = await fetch(
14 'https://raw.githubusercontent.com/pektezol/leastportalshub/main/README.md' 14 'https://raw.githubusercontent.com/pektezol/leastportalshub/typescript/RULES.md'
15 ); 15 );
16 if (!response.ok) { 16 if (!response.ok) {
17 throw new Error('Failed to fetch README'); 17 throw new Error('Failed to fetch README');
@@ -21,7 +21,7 @@ const Rules: React.FC = () => {
21 } catch (error) { 21 } catch (error) {
22 console.error('Error fetching Rules:', error); 22 console.error('Error fetching Rules:', error);
23 } 23 }
24 setRulesText(rulesText) 24 // setRulesText(rulesText)
25 }; 25 };
26 fetchRules(); 26 fetchRules();
27 }, []); 27 }, []);
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx
index 1605ada..e3781a3 100644
--- a/frontend/src/pages/User.tsx
+++ b/frontend/src/pages/User.tsx
@@ -1,5 +1,5 @@
1import React from 'react'; 1import React from 'react';
2import { useLocation } from 'react-router-dom'; 2import { Link, useLocation, useNavigate } from 'react-router-dom';
3 3
4import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; 4import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images';
5import { UserProfile } from '../types/Profile'; 5import { UserProfile } from '../types/Profile';
@@ -9,8 +9,13 @@ import { API } from '../api/Api';
9import { ticks_to_time } from '../utils/Time'; 9import { ticks_to_time } from '../utils/Time';
10import "../css/Profile.css"; 10import "../css/Profile.css";
11 11
12const User: React.FC = () => { 12interface UserProps {
13 const location = useLocation(); 13 profile?: UserProfile;
14 token?: string;
15 gameData: Game[];
16}
17
18const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
14 19
15 const [user, setUser] = React.useState<UserProfile | undefined>(undefined); 20 const [user, setUser] = React.useState<UserProfile | undefined>(undefined);
16 21
@@ -18,91 +23,63 @@ const User: React.FC = () => {
18 const [pageNumber, setPageNumber] = React.useState(1); 23 const [pageNumber, setPageNumber] = React.useState(1);
19 const [pageMax, setPageMax] = React.useState(0); 24 const [pageMax, setPageMax] = React.useState(0);
20 25
21 const [game, setGame] = React.useState("0") 26 const [game, setGame] = React.useState("0");
22 const [gameData, setGameData] = React.useState<Game[]>([]); 27 const [chapter, setChapter] = React.useState("0");
23 const [chapter, setChapter] = React.useState("0")
24 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); 28 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null);
25 const [maps, setMaps] = React.useState<Map[]>([]); 29 const [maps, setMaps] = React.useState<Map[]>([]);
26 30
27 function NavClick() { 31 const location = useLocation();
28 if (user) { 32 const navigate = useNavigate();
29 const btn = document.querySelectorAll("#section2 button");
30 btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" });
31 (btn[navState] as HTMLElement).style.backgroundColor = "#202232";
32 33
33 document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "") 34 const _fetch_user = async () => {
34 if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); } 35 const userID = location.pathname.split("/")[2];
35 if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); } 36 if (token && profile && profile.profile && profile.steam_id === userID) {
37 navigate("/profile");
38 return;
36 } 39 }
37 } 40 const userData = await API.get_user(userID);
41 setUser(userData);
42 };
38 43
39 function UpdateProfile() { 44 const _get_game_chapters = async () => {
40 fetch(`https://lp.ardapektezol.com/api/v1/profile`, { 45 if (game !== "0") {
41 method: 'POST', 46 const gameChapters = await API.get_games_chapters(game);
42 headers: { Authorization: "" } 47 setChapterData(gameChapters);
43 }).then(r => r.json()) 48 } else {
44 .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`)) 49 setPageMax(Math.ceil(user!.records.length / 20));
45 } 50 setPageNumber(1);
51 }
52 };
46 53
47 const _fetch_user = async () => { 54 const _get_game_maps = async () => {
48 const userData = await API.get_user(location.pathname.split("/")[2]); 55 if (chapter === "0") {
49 setUser(userData); 56 const gameMaps = await API.get_game_maps(game);
57 setMaps(gameMaps);
58 setPageMax(Math.ceil(gameMaps.length / 20));
59 setPageNumber(1);
60 } else {
61 const gameChapters = await API.get_chapters(chapter);
62 setMaps(gameChapters.maps);
63 setPageMax(Math.ceil(gameChapters.maps.length / 20));
64 setPageNumber(1);
65 }
50 }; 66 };
51 67
52 React.useEffect(() => { 68 React.useEffect(() => {
53 fetch("https://lp.ardapektezol.com/api/v1/games") 69 _fetch_user();
54 .then(r => r.json())
55 .then(d => {
56 setGameData(d.data)
57 setGame("0")
58 })
59
60 }, [location]); 70 }, [location]);
61 71
62 React.useEffect(() => { 72 React.useEffect(() => {
63 if (user) { 73 if (user) {
64 if (game && game !== "0") { 74 _get_game_chapters();
65 fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`)
66 .then(r => r.json())
67 .then(d => {
68 setChapterData(d.data)
69 setChapter("0");
70 // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0"
71 })
72
73 } else if (game && game === "0") {
74 setPageMax(Math.ceil(user.records.length / 20))
75 setPageNumber(1)
76 }
77 } 75 }
78 }, [user, game, location]); 76 }, [user, game, location]);
79 77
80 React.useEffect(() => { 78 React.useEffect(() => {
81 _fetch_user(); 79 if (user && game !== "0") {
82 }, [user]); 80 _get_game_maps();
83
84 React.useEffect(() => {
85 if (game !== "0") {
86 if (chapter === "0") {
87 fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`)
88 .then(r => r.json())
89 .then(d => {
90 setMaps(d.data.maps);
91 setPageMax(Math.ceil(d.data.maps.length / 20))
92 setPageNumber(1)
93 })
94 } else {
95 fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`)
96 .then(r => r.json())
97 .then(d => {
98 setMaps(d.data.maps);
99 setPageMax(Math.ceil(d.data.maps.length / 20))
100 setPageNumber(1)
101 })
102
103 }
104 } 81 }
105 }, [game, chapter, chapterData]) 82 }, [user, game, chapter, location])
106 83
107 if (!user) { 84 if (!user) {
108 return ( 85 return (
@@ -113,19 +90,9 @@ const User: React.FC = () => {
113 return ( 90 return (
114 <main> 91 <main>
115 <section id='section1' className='profile'> 92 <section id='section1' className='profile'>
116 93 <div>
117 {user.profile 94 <img src={user.avatar_link} alt="profile-image"></img>
118 ? ( 95 </div>
119 <div id='profile-image' onClick={() => UpdateProfile()}>
120 <img src={user.avatar_link} alt="profile-image"></img>
121 <span>Refresh</span>
122 </div>
123 ) : (
124 <div>
125 <img src={user.avatar_link} alt="profile-image"></img>
126 </div>
127 )}
128
129 <div id='profile-top'> 96 <div id='profile-top'>
130 <div> 97 <div>
131 <div>{user.user_name}</div> 98 <div>{user.user_name}</div>
@@ -185,7 +152,14 @@ const User: React.FC = () => {
185 {gameData === null ? <select>error</select> : 152 {gameData === null ? <select>error</select> :
186 153
187 <select id='select-game' 154 <select id='select-game'
188 onChange={() => setGame((document.querySelector('#select-game') as HTMLInputElement).value)}> 155 onChange={() => {
156 setGame((document.querySelector('#select-game') as HTMLInputElement).value);
157 setChapter("0");
158 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement;
159 if (chapterSelect) {
160 chapterSelect.value = "0";
161 }
162 }}>
189 <option value={0} key={0}>All Scores</option> 163 <option value={0} key={0}>All Scores</option>
190 {gameData.map((e, i) => ( 164 {gameData.map((e, i) => (
191 <option value={e.id} key={i + 1}>{e.name}</option> 165 <option value={e.id} key={i + 1}>{e.name}</option>
@@ -238,7 +212,7 @@ const User: React.FC = () => {
238 {r.scores.map((e, i) => (<> 212 {r.scores.map((e, i) => (<>
239 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 213 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
240 214
241 <span>{r.map_name}</span> 215 <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link>
242 216
243 <span style={{ display: "grid" }}>{e.score_count}</span> 217 <span style={{ display: "grid" }}>{e.score_count}</span>
244 218
@@ -250,7 +224,7 @@ const User: React.FC = () => {
250 <span style={{ flexDirection: "row-reverse" }}> 224 <span style={{ flexDirection: "row-reverse" }}>
251 225
252 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 226 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
253 <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 227 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
254 {i === 0 && r.scores.length > 1 ? <button onClick={() => { 228 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
255 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 229 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
256 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 230 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
@@ -295,7 +269,7 @@ const User: React.FC = () => {
295 <span style={{ flexDirection: "row-reverse" }}> 269 <span style={{ flexDirection: "row-reverse" }}>
296 270
297 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 271 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
298 <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 272 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
299 {i === 0 && record!.scores.length > 1 ? <button onClick={() => { 273 {i === 0 && record!.scores.length > 1 ? <button onClick={() => {
300 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 274 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
301 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 275 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?