aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/pages
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2024-09-03 00:08:53 +0300
committerArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2024-09-03 00:08:53 +0300
commita65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98 (patch)
treeedf8630e9d6426124dd49854af0cb703ebc5b710 /frontend/src/pages
parentfix: revert to static homepage (#195) (diff)
downloadlphub-a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98.tar.gz
lphub-a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98.tar.bz2
lphub-a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98.zip
refactor: port to typescript
Diffstat (limited to 'frontend/src/pages')
-rw-r--r--frontend/src/pages/Games.tsx51
-rw-r--r--frontend/src/pages/Maps.tsx91
-rw-r--r--frontend/src/pages/Profile.tsx326
-rw-r--r--frontend/src/pages/User.tsx320
4 files changed, 788 insertions, 0 deletions
diff --git a/frontend/src/pages/Games.tsx b/frontend/src/pages/Games.tsx
new file mode 100644
index 0000000..e4b33e5
--- /dev/null
+++ b/frontend/src/pages/Games.tsx
@@ -0,0 +1,51 @@
1import React from 'react';
2
3import GameEntry from '../components/GameEntry';
4import { Game } from '../types/Game';
5import { API } from '../api/Api';
6import "../css/Maps.css"
7
8const Games: React.FC = () => {
9 const [games, setGames] = React.useState<Game[]>([]);
10
11 const _fetch_games = async () => {
12 const games = await API.get_games();
13 setGames(games);
14 };
15
16 const _page_load = () => {
17 const loaders = document.querySelectorAll(".loader");
18 loaders.forEach((loader) => {
19 (loader as HTMLElement).style.display = "none";
20 });
21 }
22
23 React.useEffect(() => {
24 document.querySelectorAll(".games-page-item-body").forEach((game, index) => {
25 game.innerHTML = "";
26 });
27
28 _fetch_games();
29 _page_load();
30 }, []);
31
32 return (
33 <div className='games-page'>
34 <section className='games-page-header'>
35 <span><b>Games list</b></span>
36 </section>
37
38 <section>
39 <div className='games-page-content'>
40 <div className='games-page-item-content'>
41 {games.map((game, index) => (
42 <GameEntry game={game} key={index} />
43 ))}
44 </div>
45 </div>
46 </section>
47 </div>
48 );
49};
50
51export default Games;
diff --git a/frontend/src/pages/Maps.tsx b/frontend/src/pages/Maps.tsx
new file mode 100644
index 0000000..707d865
--- /dev/null
+++ b/frontend/src/pages/Maps.tsx
@@ -0,0 +1,91 @@
1import React from 'react';
2import { Link, useLocation } from 'react-router-dom';
3
4import { PortalIcon, FlagIcon, ChatIcon } from '../images/Images';
5import Summary from '../components/Summary';
6import Leaderboards from '../components/Leaderboards';
7import Discussions from '../components/Discussions';
8import ModMenu from '../components/ModMenu';
9import { MapDiscussions, MapLeaderboard, MapSummary } from '../types/Map';
10import { API } from '../api/Api';
11import "../css/Maps.css";
12
13interface MapProps {
14 isModerator: boolean;
15};
16
17const Maps: React.FC<MapProps> = ({ isModerator }) => {
18
19 const [selectedRun, setSelectedRun] = React.useState<number>(0);
20
21 const [mapSummaryData, setMapSummaryData] = React.useState<MapSummary | undefined>(undefined);
22 const [mapLeaderboardData, setMapLeaderboardData] = React.useState<MapLeaderboard | undefined>(undefined);
23 const [mapDiscussionsData, setMapDiscussionsData] = React.useState<MapDiscussions | undefined>(undefined)
24
25
26 const [navState, setNavState] = React.useState<number>(0);
27
28 const location = useLocation();
29
30 const mapID = location.pathname.split("/")[2];
31
32 const _fetch_map_summary = async () => {
33 const mapSummary = await API.get_map_summary(mapID);
34 setMapSummaryData(mapSummary);
35 };
36
37 const _fetch_map_leaderboards = async () => {
38 const mapLeaderboards = await API.get_map_leaderboard(mapID);
39 setMapLeaderboardData(mapLeaderboards);
40 };
41
42 const _fetch_map_discussions = async () => {
43 const mapDiscussions = await API.get_map_discussions(mapID);
44 setMapDiscussionsData(mapDiscussions);
45 };
46
47 React.useEffect(() => {
48 _fetch_map_summary();
49 _fetch_map_leaderboards();
50 _fetch_map_discussions();
51 }, []);
52
53 if (!mapSummaryData) {
54 return (
55 <></>
56 );
57 }
58
59 return (
60 <>
61 {isModerator && <ModMenu data={mapSummaryData} selectedRun={selectedRun} mapID={mapID} />}
62
63 <div id='background-image'>
64 <img src={mapSummaryData.map.image} alt="" />
65 </div>
66 <main>
67 <section id='section1' className='summary1'>
68 <div>
69 <Link to="/games"><button className='nav-button' style={{ borderRadius: "20px 0px 0px 20px" }}><i className='triangle'></i><span>Games list</span></button></Link>
70 <Link to={`/games/${!mapSummaryData.map.is_coop ? "1" : "2"}?chapter=${mapSummaryData.map.chapter_name}`}><button className='nav-button' style={{ borderRadius: "0px 20px 20px 0px", marginLeft: "2px" }}><i className='triangle'></i><span>{mapSummaryData.map.chapter_name}</span></button></Link>
71 <br /><span><b>{mapSummaryData.map.map_name}</b></span>
72 </div>
73
74
75 </section>
76
77 <section id='section2' className='summary1'>
78 <button className='nav-button' onClick={() => setNavState(0)}><img src={PortalIcon} alt="" /><span>Summary</span></button>
79 <button className='nav-button' onClick={() => setNavState(1)}><img src={FlagIcon} alt="" /><span>Leaderboards</span></button>
80 <button className='nav-button' onClick={() => setNavState(2)}><img src={ChatIcon} alt="" /><span>Discussions</span></button>
81 </section>
82
83 {navState === 0 && <Summary selectedRun={selectedRun} setSelectedRun={setSelectedRun} data={mapSummaryData} />}
84 {navState === 1 && <Leaderboards data={mapLeaderboardData} />}
85 {navState === 2 && <Discussions data={mapDiscussionsData} isModerator={isModerator} mapID={mapID} onRefresh={() => _fetch_map_discussions()} />}
86 </main>
87 </>
88 );
89};
90
91export default Maps;
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
new file mode 100644
index 0000000..8654a03
--- /dev/null
+++ b/frontend/src/pages/Profile.tsx
@@ -0,0 +1,326 @@
1import React from 'react';
2import { useLocation, useNavigate } from 'react-router-dom';
3
4import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images';
5import { UserProfile } from '../types/Profile';
6import { Game, GameChapters } from '../types/Game';
7import { Map } from '../types/Map';
8import "../css/Profile.css";
9
10interface ProfileProps {
11 profile: UserProfile;
12}
13
14const Profile: React.FC<ProfileProps> = ({ profile }) => {
15
16
17 const location = useLocation();
18 const navigate = useNavigate();
19
20 React.useEffect(() => {
21 if (!profile) {
22 navigate("/");
23 };
24 }, [profile]);
25
26 const [navState, setNavState] = React.useState(0);
27 const [pageNumber, setPageNumber] = React.useState(1);
28 const [pageMax, setPageMax] = React.useState(0);
29
30 const [game, setGame] = React.useState("0")
31 const [gameData, setGameData] = React.useState<Game[]>([]);
32 const [chapter, setChapter] = React.useState("0")
33 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null);
34 const [maps, setMaps] = React.useState<Map[]>([]);
35
36 function NavClick() {
37 if (profile) {
38 const btn = document.querySelectorAll("#section2 button");
39 btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" });
40 (btn[navState] as HTMLElement).style.backgroundColor = "#202232";
41
42 document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "")
43 if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); }
44 if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); }
45 }
46 }
47
48 function UpdateProfile() {
49 fetch(`https://lp.ardapektezol.com/api/v1/profile`, {
50 method: 'POST',
51 headers: { Authorization: "" }
52 }).then(r => r.json())
53 .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`))
54 }
55
56 function TicksToTime(ticks: number) {
57
58 let seconds = Math.floor(ticks / 60)
59 let minutes = Math.floor(seconds / 60)
60 let hours = Math.floor(minutes / 60)
61
62 let milliseconds = Math.floor((ticks % 60) * 1000 / 60)
63 seconds = seconds % 60;
64 minutes = minutes % 60;
65
66 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')} (${ticks})`;
67 }
68
69 React.useEffect(() => {
70 fetch("https://lp.ardapektezol.com/api/v1/games")
71 .then(r => r.json())
72 .then(d => {
73 setGameData(d.data)
74 setGame("0")
75 })
76
77 }, [location]);
78
79 React.useEffect(() => {
80 if (game && game !== "0") {
81 fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`)
82 .then(r => r.json())
83 .then(d => {
84 setChapterData(d.data)
85 setChapter("0");
86 // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0"
87 })
88
89 } else if (game && game === "0") {
90 setPageMax(Math.ceil(profile.records.length / 20))
91 setPageNumber(1)
92 }
93
94 }, [game, location]);
95
96 React.useEffect(() => {
97 if (game !== "0") {
98 if (chapter === "0") {
99 fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`)
100 .then(r => r.json())
101 .then(d => {
102 setMaps(d.data.maps);
103 setPageMax(Math.ceil(d.data.maps.length / 20))
104 setPageNumber(1)
105 })
106 } else {
107 fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`)
108 .then(r => r.json())
109 .then(d => {
110 setMaps(d.data.maps);
111 setPageMax(Math.ceil(d.data.maps.length / 20))
112 setPageNumber(1)
113 })
114
115 }
116 }
117 }, [game, chapter, chapterData])
118
119 return (
120 <main>
121 <section id='section1' className='profile'>
122
123 {profile.profile
124 ? (
125 <div id='profile-image' onClick={() => UpdateProfile()}>
126 <img src={profile.avatar_link} alt="profile-image"></img>
127 <span>Refresh</span>
128 </div>
129 ) : (
130 <div>
131 <img src={profile.avatar_link} alt="profile-image"></img>
132 </div>
133 )}
134
135 <div id='profile-top'>
136 <div>
137 <div>{profile.user_name}</div>
138 <div>
139 {profile.country_code === "XX" ? "" : <img src={`https://flagcdn.com/w80/${profile.country_code.toLowerCase()}.jpg`} alt={profile.country_code} />}
140 </div>
141 <div>
142 {profile.titles.map(e => (
143 <span className="titles" style={{ backgroundColor: `#${e.color}` }}>
144 {e.name}
145 </span>
146 ))}
147 </div>
148 </div>
149 <div>
150 {profile.links.steam === "-" ? "" : <a href={profile.links.steam}><img src={SteamIcon} alt="Steam" /></a>}
151 {profile.links.twitch === "-" ? "" : <a href={profile.links.twitch}><img src={TwitchIcon} alt="Twitch" /></a>}
152 {profile.links.youtube === "-" ? "" : <a href={profile.links.youtube}><img src={YouTubeIcon} alt="Youtube" /></a>}
153 {profile.links.p2sr === "-" ? "" : <a href={profile.links.p2sr}><img src={PortalIcon} alt="P2SR" style={{ padding: "0" }} /></a>}
154 </div>
155
156 </div>
157 <div id='profile-bottom'>
158 <div>
159 <span>Overall</span>
160 <span>{profile.rankings.overall.rank === 0 ? "N/A " : "#" + profile.rankings.overall.rank + " "}
161 <span>({profile.rankings.overall.completion_count}/{profile.rankings.overall.completion_total})</span>
162 </span>
163 </div>
164 <div>
165 <span>Singleplayer</span>
166 <span>{profile.rankings.singleplayer.rank === 0 ? "N/A " : "#" + profile.rankings.singleplayer.rank + " "}
167 <span>({profile.rankings.singleplayer.completion_count}/{profile.rankings.singleplayer.completion_total})</span>
168 </span>
169 </div>
170 <div>
171 <span>Cooperative</span>
172 <span>{profile.rankings.cooperative.rank === 0 ? "N/A " : "#" + profile.rankings.cooperative.rank + " "}
173 <span>({profile.rankings.cooperative.completion_count}/{profile.rankings.cooperative.completion_total})</span>
174 </span>
175 </div>
176 </div>
177 </section>
178
179
180 <section id='section2' className='profile'>
181 <button onClick={() => setNavState(0)}><img src={FlagIcon} alt="" />&nbsp;Player Records</button>
182 <button onClick={() => setNavState(1)}><img src={StatisticsIcon} alt="" />&nbsp;Statistics</button>
183 </section>
184
185
186
187
188
189 <section id='section3' className='profile1'>
190 <div id='profileboard-nav'>
191 {gameData === null ? <select>error</select> :
192
193 <select id='select-game'
194 onChange={() => setGame((document.querySelector('#select-game') as HTMLInputElement).value)}>
195 <option value={0} key={0}>All Scores</option>
196 {gameData.map((e, i) => (
197 <option value={e.id} key={i + 1}>{e.name}</option>
198 ))}</select>
199 }
200
201 {game === "0" ?
202 <select disabled>
203 <option>All Scores</option>
204 </select>
205 : chapterData === null ? <select></select> :
206
207 <select id='select-chapter'
208 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}>
209 <option value="0" key="0">All</option>
210 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => (
211 <option value={e.id} key={i + 1}>{e.name}</option>
212 ))}</select>
213 }
214 </div>
215 <div id='profileboard-top'>
216 <span><span>Map Name</span><img src={SortIcon} alt="" /></span>
217 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span>
218 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span>
219 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span>
220 <span> </span>
221 <span><span>Rank</span><img src={SortIcon} alt="" /></span>
222 <span><span>Date</span><img src={SortIcon} alt="" /></span>
223 <div id='page-number'>
224 <div>
225 <button onClick={() => pageNumber === 1 ? null : setPageNumber(prevPageNumber => prevPageNumber - 1)}
226 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button>
227 <span>{pageNumber}/{pageMax}</span>
228 <button onClick={() => pageNumber === pageMax ? null : setPageNumber(prevPageNumber => prevPageNumber + 1)}
229 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button>
230 </div>
231 </div>
232 </div>
233 <hr />
234 <div id='profileboard-records'>
235
236 {game === "0"
237 ? (
238
239 profile.records.sort((a, b) => a.map_id - b.map_id)
240 .map((r, index) => (
241
242 Math.ceil((index + 1) / 20) === pageNumber ? (
243 <button className="profileboard-record" key={index}>
244 {r.scores.map((e, i) => (<>
245 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
246
247 <span>{r.map_name}</span>
248
249 <span style={{ display: "grid" }}>{e.score_count}</span>
250
251 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count}</span>
252 <span style={{ display: "grid" }}>{TicksToTime(e.score_time)}</span>
253 <span> </span>
254 {i === 0 ? <span>#{r.placement}</span> : <span> </span>}
255 <span>{e.date.split("T")[0]}</span>
256 <span style={{ flexDirection: "row-reverse" }}>
257
258 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
259 <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
260 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
261 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
262 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
263 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${r.scores.length * 46}px` :
264 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
265 }
266 }><img src={HistoryIcon} alt="history" /></button> : ""}
267
268 </span>
269 </>))}
270
271 </button>
272 ) : ""
273 ))) : maps ?
274
275 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id)
276 .map((r, index) => {
277 if (Math.ceil((index + 1) / 20) === pageNumber) {
278 let record = profile.records.find((e) => e.map_id === r.id);
279 return record === undefined ? (
280 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}>
281 <span>{r.name}</span>
282 <span style={{ display: "grid" }}>N/A</span>
283 <span style={{ display: "grid" }}>N/A</span>
284 <span>N/A</span>
285 <span> </span>
286 <span>N/A</span>
287 <span>N/A</span>
288 <span style={{ flexDirection: "row-reverse" }}></span>
289 </button>
290 ) : (
291 <button className="profileboard-record" key={index}>
292 {record.scores.map((e, i) => (<>
293 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
294 <span>{r.name}</span>
295 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span>
296 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count}</span>
297 <span style={{ display: "grid" }}>{TicksToTime(record!.scores[i].score_time)}</span>
298 <span> </span>
299 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>}
300 <span>{record!.scores[i].date.split("T")[0]}</span>
301 <span style={{ flexDirection: "row-reverse" }}>
302
303 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
304 <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
305 {i === 0 && record!.scores.length > 1 ? <button onClick={() => {
306 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
307 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
308 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${record!.scores.length * 46}px` :
309 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
310 }
311 }><img src={HistoryIcon} alt="history" /></button> : ""}
312
313 </span>
314 </>))}
315 </button>
316
317 )
318 } else { return null }
319 }) : (<>{console.warn(maps)}</>)}
320 </div>
321 </section>
322 </main>
323 );
324};
325
326export default Profile;
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx
new file mode 100644
index 0000000..1f6d8d0
--- /dev/null
+++ b/frontend/src/pages/User.tsx
@@ -0,0 +1,320 @@
1import React from 'react';
2import { useLocation } from 'react-router-dom';
3
4import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images';
5import { UserProfile } from '../types/Profile';
6import { Game, GameChapters } from '../types/Game';
7import { Map } from '../types/Map';
8import { API } from '../api/Api';
9import { ticks_to_time } from '../utils/Time';
10import "../css/Profile.css";
11
12const User: React.FC = () => {
13 const location = useLocation();
14
15 const [user, setUser] = React.useState<UserProfile | undefined>(undefined);
16
17 const [navState, setNavState] = React.useState(0);
18 const [pageNumber, setPageNumber] = React.useState(1);
19 const [pageMax, setPageMax] = React.useState(0);
20
21 const [game, setGame] = React.useState("0")
22 const [gameData, setGameData] = React.useState<Game[]>([]);
23 const [chapter, setChapter] = React.useState("0")
24 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null);
25 const [maps, setMaps] = React.useState<Map[]>([]);
26
27 function NavClick() {
28 if (user) {
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 document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "")
34 if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); }
35 if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); }
36 }
37 }
38
39 function UpdateProfile() {
40 fetch(`https://lp.ardapektezol.com/api/v1/profile`, {
41 method: 'POST',
42 headers: { Authorization: "" }
43 }).then(r => r.json())
44 .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`))
45 }
46
47 const _fetch_user = async () => {
48 const userData = await API.get_user(location.pathname.split("/")[2]);
49 setUser(userData);
50 };
51
52 React.useEffect(() => {
53 fetch("https://lp.ardapektezol.com/api/v1/games")
54 .then(r => r.json())
55 .then(d => {
56 setGameData(d.data)
57 setGame("0")
58 })
59
60 }, [location]);
61
62 React.useEffect(() => {
63 if (user) {
64 if (game && game !== "0") {
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 }
78 }, [user, game, location]);
79
80 React.useEffect(() => {
81 _fetch_user();
82 }, []);
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 }
105 }, [game, chapter, chapterData])
106
107 if (!user) {
108 return (
109 <></>
110 );
111 };
112
113 return (
114 <main>
115 <section id='section1' className='profile'>
116
117 {user.profile
118 ? (
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'>
130 <div>
131 <div>{user.user_name}</div>
132 <div>
133 {user.country_code === "XX" ? "" : <img src={`https://flagcdn.com/w80/${user.country_code.toLowerCase()}.jpg`} alt={user.country_code} />}
134 </div>
135 <div>
136 {user.titles.map(e => (
137 <span className="titles" style={{ backgroundColor: `#${e.color}` }}>
138 {e.name}
139 </span>
140 ))}
141 </div>
142 </div>
143 <div>
144 {user.links.steam === "-" ? "" : <a href={user.links.steam}><img src={SteamIcon} alt="Steam" /></a>}
145 {user.links.twitch === "-" ? "" : <a href={user.links.twitch}><img src={TwitchIcon} alt="Twitch" /></a>}
146 {user.links.youtube === "-" ? "" : <a href={user.links.youtube}><img src={YouTubeIcon} alt="Youtube" /></a>}
147 {user.links.p2sr === "-" ? "" : <a href={user.links.p2sr}><img src={PortalIcon} alt="P2SR" style={{ padding: "0" }} /></a>}
148 </div>
149
150 </div>
151 <div id='profile-bottom'>
152 <div>
153 <span>Overall</span>
154 <span>{user.rankings.overall.rank === 0 ? "N/A " : "#" + user.rankings.overall.rank + " "}
155 <span>({user.rankings.overall.completion_count}/{user.rankings.overall.completion_total})</span>
156 </span>
157 </div>
158 <div>
159 <span>Singleplayer</span>
160 <span>{user.rankings.singleplayer.rank === 0 ? "N/A " : "#" + user.rankings.singleplayer.rank + " "}
161 <span>({user.rankings.singleplayer.completion_count}/{user.rankings.singleplayer.completion_total})</span>
162 </span>
163 </div>
164 <div>
165 <span>Cooperative</span>
166 <span>{user.rankings.cooperative.rank === 0 ? "N/A " : "#" + user.rankings.cooperative.rank + " "}
167 <span>({user.rankings.cooperative.completion_count}/{user.rankings.cooperative.completion_total})</span>
168 </span>
169 </div>
170 </div>
171 </section>
172
173
174 <section id='section2' className='profile'>
175 <button onClick={() => setNavState(0)}><img src={FlagIcon} alt="" />&nbsp;Player Records</button>
176 <button onClick={() => setNavState(1)}><img src={StatisticsIcon} alt="" />&nbsp;Statistics</button>
177 </section>
178
179
180
181
182
183 <section id='section3' className='profile1'>
184 <div id='profileboard-nav'>
185 {gameData === null ? <select>error</select> :
186
187 <select id='select-game'
188 onChange={() => setGame((document.querySelector('#select-game') as HTMLInputElement).value)}>
189 <option value={0} key={0}>All Scores</option>
190 {gameData.map((e, i) => (
191 <option value={e.id} key={i + 1}>{e.name}</option>
192 ))}</select>
193 }
194
195 {game === "0" ?
196 <select disabled>
197 <option>All Scores</option>
198 </select>
199 : chapterData === null ? <select></select> :
200
201 <select id='select-chapter'
202 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}>
203 <option value="0" key="0">All</option>
204 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => (
205 <option value={e.id} key={i + 1}>{e.name}</option>
206 ))}</select>
207 }
208 </div>
209 <div id='profileboard-top'>
210 <span><span>Map Name</span><img src={SortIcon} alt="" /></span>
211 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span>
212 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span>
213 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span>
214 <span> </span>
215 <span><span>Rank</span><img src={SortIcon} alt="" /></span>
216 <span><span>Date</span><img src={SortIcon} alt="" /></span>
217 <div id='page-number'>
218 <div>
219 <button onClick={() => pageNumber === 1 ? null : setPageNumber(prevPageNumber => prevPageNumber - 1)}
220 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button>
221 <span>{pageNumber}/{pageMax}</span>
222 <button onClick={() => pageNumber === pageMax ? null : setPageNumber(prevPageNumber => prevPageNumber + 1)}
223 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button>
224 </div>
225 </div>
226 </div>
227 <hr />
228 <div id='profileboard-records'>
229
230 {game === "0"
231 ? (
232
233 user.records.sort((a, b) => a.map_id - b.map_id)
234 .map((r, index) => (
235
236 Math.ceil((index + 1) / 20) === pageNumber ? (
237 <button className="profileboard-record" key={index}>
238 {r.scores.map((e, i) => (<>
239 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
240
241 <span>{r.map_name}</span>
242
243 <span style={{ display: "grid" }}>{e.score_count}</span>
244
245 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count}</span>
246 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span>
247 <span> </span>
248 {i === 0 ? <span>#{r.placement}</span> : <span> </span>}
249 <span>{e.date.split("T")[0]}</span>
250 <span style={{ flexDirection: "row-reverse" }}>
251
252 <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>
254 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
255 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
256 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
257 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${r.scores.length * 46}px` :
258 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
259 }
260 }><img src={HistoryIcon} alt="history" /></button> : ""}
261
262 </span>
263 </>))}
264
265 </button>
266 ) : ""
267 ))) : maps ?
268
269 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id)
270 .map((r, index) => {
271 if (Math.ceil((index + 1) / 20) === pageNumber) {
272 let record = user.records.find((e) => e.map_id === r.id);
273 return record === undefined ? (
274 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}>
275 <span>{r.name}</span>
276 <span style={{ display: "grid" }}>N/A</span>
277 <span style={{ display: "grid" }}>N/A</span>
278 <span>N/A</span>
279 <span> </span>
280 <span>N/A</span>
281 <span>N/A</span>
282 <span style={{ flexDirection: "row-reverse" }}></span>
283 </button>
284 ) : (
285 <button className="profileboard-record" key={index}>
286 {record.scores.map((e, i) => (<>
287 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
288 <span>{r.name}</span>
289 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span>
290 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count}</span>
291 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span>
292 <span> </span>
293 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>}
294 <span>{record!.scores[i].date.split("T")[0]}</span>
295 <span style={{ flexDirection: "row-reverse" }}>
296
297 <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>
299 {i === 0 && record!.scores.length > 1 ? <button onClick={() => {
300 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
301 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
302 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${record!.scores.length * 46}px` :
303 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
304 }
305 }><img src={HistoryIcon} alt="history" /></button> : ""}
306
307 </span>
308 </>))}
309 </button>
310
311 )
312 } else { return null }
313 }) : (<>{console.warn(maps)}</>)}
314 </div>
315 </section>
316 </main>
317 );
318};
319
320export default User;