aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/pages/Profile.tsx
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/Profile.tsx
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/Profile.tsx')
-rw-r--r--frontend/src/pages/Profile.tsx326
1 files changed, 326 insertions, 0 deletions
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;