aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/pages/User.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/pages/User.tsx')
-rw-r--r--frontend/src/pages/User.tsx525
1 files changed, 308 insertions, 217 deletions
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx
index d43c0c6..8c699b1 100644
--- a/frontend/src/pages/User.tsx
+++ b/frontend/src/pages/User.tsx
@@ -1,15 +1,25 @@
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 {
6import { UserProfile } from '@customTypes/Profile'; 6 SteamIcon,
7import { Game, GameChapters } from '@customTypes/Game'; 7 TwitchIcon,
8import { Map } from '@customTypes/Map'; 8 YouTubeIcon,
9import { API } from '@api/Api'; 9 PortalIcon,
10import { ticks_to_time } from '@utils/Time'; 10 FlagIcon,
11import "@css/Profile.css"; 11 StatisticsIcon,
12import useMessage from '@hooks/UseMessage'; 12 SortIcon,
13 ThreedotIcon,
14 DownloadIcon,
15 HistoryIcon,
16} from "@images/Images";
17import { UserProfile } from "@customTypes/Profile";
18import { Game, GameChapters } from "@customTypes/Game";
19import { Map } from "@customTypes/Map";
20import { API } from "@api/Api";
21import { ticks_to_time } from "@utils/Time";
22import useMessage from "@hooks/UseMessage";
13 23
14interface UserProps { 24interface UserProps {
15 profile?: UserProfile; 25 profile?: UserProfile;
@@ -18,7 +28,6 @@ interface UserProps {
18} 28}
19 29
20const User: React.FC<UserProps> = ({ token, profile, gameData }) => { 30const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
21
22 const { message, MessageDialogComponent } = useMessage(); 31 const { message, MessageDialogComponent } = useMessage();
23 32
24 const [user, setUser] = React.useState<UserProfile | undefined>(undefined); 33 const [user, setUser] = React.useState<UserProfile | undefined>(undefined);
@@ -29,13 +38,15 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
29 38
30 const [game, setGame] = React.useState("0"); 39 const [game, setGame] = React.useState("0");
31 const [chapter, setChapter] = React.useState("0"); 40 const [chapter, setChapter] = React.useState("0");
32 const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); 41 const [chapterData, setChapterData] = React.useState<GameChapters | null>(
42 null
43 );
33 const [maps, setMaps] = React.useState<Map[]>([]); 44 const [maps, setMaps] = React.useState<Map[]>([]);
34 45
35 const location = useLocation(); 46 const location = useLocation();
36 const navigate = useNavigate(); 47 const navigate = useNavigate();
37 48
38 const _fetch_user = async () => { 49 const _fetch_user = React.useCallback(async () => {
39 const userID = location.pathname.split("/")[2]; 50 const userID = location.pathname.split("/")[2];
40 if (token && profile && profile.profile && profile.steam_id === userID) { 51 if (token && profile && profile.profile && profile.steam_id === userID) {
41 navigate("/profile"); 52 navigate("/profile");
@@ -43,9 +54,9 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
43 } 54 }
44 const userData = await API.get_user(userID); 55 const userData = await API.get_user(userID);
45 setUser(userData); 56 setUser(userData);
46 }; 57 }, [location.pathname, token, profile, navigate]);
47 58
48 const _get_game_chapters = async () => { 59 const _get_game_chapters = React.useCallback(async () => {
49 if (game !== "0") { 60 if (game !== "0") {
50 const gameChapters = await API.get_games_chapters(game); 61 const gameChapters = await API.get_games_chapters(game);
51 setChapterData(gameChapters); 62 setChapterData(gameChapters);
@@ -53,9 +64,9 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
53 setPageMax(Math.ceil(user!.records.length / 20)); 64 setPageMax(Math.ceil(user!.records.length / 20));
54 setPageNumber(1); 65 setPageNumber(1);
55 } 66 }
56 }; 67 }, [game, user]);
57 68
58 const _get_game_maps = async () => { 69 const _get_game_maps = React.useCallback(async () => {
59 if (chapter === "0") { 70 if (chapter === "0") {
60 const gameMaps = await API.get_game_maps(game); 71 const gameMaps = await API.get_game_maps(game);
61 setMaps(gameMaps); 72 setMaps(gameMaps);
@@ -67,251 +78,331 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
67 setPageMax(Math.ceil(gameChapters.maps.length / 20)); 78 setPageMax(Math.ceil(gameChapters.maps.length / 20));
68 setPageNumber(1); 79 setPageNumber(1);
69 } 80 }
70 }; 81 }, [chapter, game]);
71 82
72 React.useEffect(() => { 83 React.useEffect(() => {
73 _fetch_user(); 84 _fetch_user();
74 }, [location]); 85 }, [location, _fetch_user]);
75 86
76 React.useEffect(() => { 87 React.useEffect(() => {
77 if (user) { 88 if (user) {
78 _get_game_chapters(); 89 _get_game_chapters();
79 } 90 }
80 }, [user, game, location]); 91 }, [user, game, location, _get_game_chapters]);
81 92
82 React.useEffect(() => { 93 React.useEffect(() => {
83 if (user && game !== "0") { 94 if (user && game !== "0") {
84 _get_game_maps(); 95 _get_game_maps();
85 } 96 }
86 }, [user, game, chapter, location]) 97 }, [user, game, chapter, location, _get_game_maps]);
87 98
88 if (!user) { 99 if (!user) {
89 return ( 100 return (
90 <></> 101 <div className="flex justify-center items-center h-[50vh] text-lg text-foreground">
102 Loading...
103 </div>
91 ); 104 );
92 }; 105 }
93 106
94 return ( 107 return (
95 <main> 108 <main className="ml-20 overflow-auto overflow-x-hidden relative w-[calc(100%px)] h-screen font-[--font-barlow-semicondensed-regular] text-foreground text-xl">
96 <Helmet> 109 <Helmet>
97 <title>LPHUB | {user.user_name}</title> 110 <title>LPHUB | {user.user_name}</title>
98 <meta name="description" content={user.user_name} /> 111 <meta name="description" content={user.user_name} />
99 </Helmet> 112 </Helmet>
113
100 {MessageDialogComponent} 114 {MessageDialogComponent}
101 <section id='section1' className='profile'> 115
102 <div> 116 <section className="m-5 bg-gradient-to-t from-[#202232] from-50% to-[#2b2e46] to-50% rounded-3xl p-[30px] mb-[30px] text-foreground">
103 <img src={user.avatar_link} alt="profile-image"></img> 117 <div className="grid grid-cols-[200px_1fr_auto] items-center gap-[25px] mb-[25px]">
104 </div> 118 <img
105 <div id='profile-top'> 119 src={user.avatar_link}
120 alt="Profile"
121 className="w-[120px] h-[120px] rounded-full border-[3px] border-[rgba(205,207,223,0.2)]"
122 />
106 <div> 123 <div>
107 <div>{user.user_name}</div> 124 <h1 className="m-0 mb-[10px] text-[50px] font-bold text-white font-[--font-barlow-semicondensed-regular]">
108 <div> 125 {user.user_name}
109 {user.country_code === "XX" ? "" : <img src={`https://flagcdn.com/w80/${user.country_code.toLowerCase()}.jpg`} alt={user.country_code} />} 126 </h1>
110 </div> 127 {user.country_code !== "XX" && (
111 <div> 128 <div className="flex items-center gap-3 mb-[15px]">
112 {user.titles.map(e => ( 129 <img
113 <span className="titles" style={{ backgroundColor: `#${e.color}` }}> 130 src={`https://flagcdn.com/w80/${user.country_code.toLowerCase()}.jpg`}
114 {e.name} 131 alt={user.country_code}
132 className="w-6 h-4 rounded-[10px]"
133 />
134 <span>{user.country_code}</span>
135 </div>
136 )}
137 <div className="flex flex-wrap gap-2">
138 {user.titles.map((title, index) => (
139 <span
140 key={index}
141 className="py-[6px] px-5 pt-[6px] rounded-[10px] text-lg font-normal text-white"
142 style={{ backgroundColor: `#${title.color}` }}
143 >
144 {title.name}
115 </span> 145 </span>
116 ))} 146 ))}
117 </div> 147 </div>
118 </div> 148 </div>
119 <div> 149 <div className="flex gap-[15px] items-center pr-[10px]">
120 {user.links.steam === "-" ? "" : <a href={user.links.steam}><img src={SteamIcon} alt="Steam" /></a>} 150 {user.links.steam !== "-" && (
121 {user.links.twitch === "-" ? "" : <a href={user.links.twitch}><img src={TwitchIcon} alt="Twitch" /></a>} 151 <a href={user.links.steam} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
122 {user.links.youtube === "-" ? "" : <a href={user.links.youtube}><img src={YouTubeIcon} alt="Youtube" /></a>} 152 <img src={SteamIcon} alt="Steam" className="h-[50px] px-[5px] scale-90 brightness-200" />
123 {user.links.p2sr === "-" ? "" : <a href={user.links.p2sr}><img src={PortalIcon} alt="P2SR" style={{ padding: "0" }} /></a>} 153 </a>
154 )}
155 {user.links.twitch !== "-" && (
156 <a href={user.links.twitch} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
157 <img src={TwitchIcon} alt="Twitch" className="h-[50px] px-[5px] scale-90 brightness-200" />
158 </a>
159 )}
160 {user.links.youtube !== "-" && (
161 <a href={user.links.youtube} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
162 <img src={YouTubeIcon} alt="YouTube" className="h-[50px] px-[5px] scale-90 brightness-200" />
163 </a>
164 )}
165 {user.links.p2sr !== "-" && (
166 <a href={user.links.p2sr} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
167 <img src={PortalIcon} alt="P2SR" className="h-[50px] px-[5px] scale-90 brightness-200" />
168 </a>
169 )}
124 </div> 170 </div>
125
126 </div> 171 </div>
127 <div id='profile-bottom'> 172
128 <div> 173 <div className="grid grid-cols-3 gap-3 mt-24">
129 <span>Overall</span> 174 <div className="m-3 bg-[#2b2e46] rounded-[20px] p-5 text-center grid place-items-center grid-rows-[40%_50%]">
130 <span>{user.rankings.overall.rank === 0 ? "N/A " : "#" + user.rankings.overall.rank + " "} 175 <div className="text-inherit text-lg">Overall</div>
131 <span>({user.rankings.overall.completion_count}/{user.rankings.overall.completion_total})</span> 176 <div className="text-white text-[40px]">
132 </span> 177 {user.rankings.overall.rank === 0 ? "N/A" : `#${user.rankings.overall.rank}`}
178 </div>
179 <div className="text-white text-xl">
180 {user.rankings.overall.completion_count}/{user.rankings.overall.completion_total}
181 </div>
133 </div> 182 </div>
134 <div> 183 <div className="m-3 bg-[#2b2e46] rounded-[20px] p-5 text-center grid place-items-center grid-rows-[40%_50%]">
135 <span>Singleplayer</span> 184 <div className="text-inherit text-lg">Singleplayer</div>
136 <span>{user.rankings.singleplayer.rank === 0 ? "N/A " : "#" + user.rankings.singleplayer.rank + " "} 185 <div className="text-white text-[40px]">
137 <span>({user.rankings.singleplayer.completion_count}/{user.rankings.singleplayer.completion_total})</span> 186 {user.rankings.singleplayer.rank === 0 ? "N/A" : `#${user.rankings.singleplayer.rank}`}
138 </span> 187 </div>
188 <div className="text-white text-xl">
189 {user.rankings.singleplayer.completion_count}/{user.rankings.singleplayer.completion_total}
190 </div>
139 </div> 191 </div>
140 <div> 192 <div className="m-3 bg-[#2b2e46] rounded-[20px] p-5 text-center grid place-items-center grid-rows-[40%_50%]">
141 <span>Cooperative</span> 193 <div className="text-inherit text-lg">Cooperative</div>
142 <span>{user.rankings.cooperative.rank === 0 ? "N/A " : "#" + user.rankings.cooperative.rank + " "} 194 <div className="text-white text-[40px]">
143 <span>({user.rankings.cooperative.completion_count}/{user.rankings.cooperative.completion_total})</span> 195 {user.rankings.cooperative.rank === 0 ? "N/A" : `#${user.rankings.cooperative.rank}`}
144 </span> 196 </div>
197 <div className="text-white text-xl">
198 {user.rankings.cooperative.completion_count}/{user.rankings.cooperative.completion_total}
199 </div>
145 </div> 200 </div>
146 </div> 201 </div>
147 </section> 202 </section>
148 203
149 204 <section className="m-5 h-[60px] grid grid-cols-2">
150 <section id='section2' className='profile'> 205 <button
151 <button onClick={() => setNavState(0)}><img src={FlagIcon} alt="" />&nbsp;Player Records</button> 206 className={`flex justify-center items-center gap-2 bg-[#2b2e46] border-0 text-inherit font-inherit text-2xl cursor-pointer transition-colors duration-100 rounded-l-3xl hover:bg-[#202232] ${
152 <button onClick={() => setNavState(1)}><img src={StatisticsIcon} alt="" />&nbsp;Statistics</button> 207 navState === 0 ? 'bg-[#202232]' : ''
208 }`}
209 onClick={() => setNavState(0)}
210 >
211 <img src={FlagIcon} alt="" className="w-5 h-5 scale-[1.2]" />
212 Player Records
213 </button>
214 <button
215 className={`flex justify-center items-center gap-2 bg-[#2b2e46] border-0 text-inherit font-inherit text-2xl cursor-pointer transition-colors duration-100 rounded-r-3xl hover:bg-[#202232] ${
216 navState === 1 ? 'bg-[#202232]' : ''
217 }`}
218 onClick={() => setNavState(1)}
219 >
220 <img src={StatisticsIcon} alt="" className="w-5 h-5 scale-[1.2]" />
221 Statistics
222 </button>
153 </section> 223 </section>
154 224
155 225 {navState === 0 && (
156 226 <section className="m-5 block bg-[#202232] rounded-3xl overflow-hidden">
157 227 <div className="grid grid-cols-2 mx-5 my-5 mt-[10px] mb-5">
158 228 <select
159 <section id='section3' className='profile1'> 229 className="h-[50px] rounded-3xl text-center text-inherit font-inherit text-2xl border-0 bg-[#2b2e46] mr-[10px]"
160 <div id='profileboard-nav'> 230 value={game}
161 {gameData === null ? <select>error</select> : 231 onChange={(e) => {
162 232 setGame(e.target.value);
163 <select id='select-game'
164 onChange={() => {
165 setGame((document.querySelector('#select-game') as HTMLInputElement).value);
166 setChapter("0"); 233 setChapter("0");
167 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement; 234 }}
168 if (chapterSelect) { 235 >
169 chapterSelect.value = "0"; 236 <option value="0">All Games</option>
170 } 237 {gameData?.map((g) => (
171 }}> 238 <option key={g.id} value={g.id}>
172 <option value={0} key={0}>All Scores</option> 239 {g.name}
173 {gameData.map((e, i) => ( 240 </option>
174 <option value={e.id} key={i + 1}>{e.name}</option> 241 ))}
175 ))}</select> 242 </select>
176 }
177 243
178 {game === "0" ? 244 <select
179 <select disabled> 245 className="h-[50px] rounded-3xl text-center text-inherit font-inherit text-2xl border-0 bg-[#2b2e46] mr-[10px] disabled:opacity-50"
180 <option>All Chapters</option> 246 value={chapter}
247 onChange={(e) => setChapter(e.target.value)}
248 disabled={game === "0"}
249 >
250 <option value="0">All Chapters</option>
251 {chapterData?.chapters
252 .filter(c => !c.is_disabled)
253 .map((c) => (
254 <option key={c.id} value={c.id}>
255 {c.name}
256 </option>
257 ))}
181 </select> 258 </select>
182 : chapterData === null ? <select></select> : 259 </div>
183 260
184 <select id='select-chapter' 261 <div className="h-[34px] grid text-xl pl-[60px] mx-5 my-0 grid-cols-[15%_15%_5%_15%_5%_15%_15%_15%]">
185 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}> 262 <div className="flex place-items-end cursor-pointer">
186 <option value="0" key="0">All Chapters</option> 263 <span>Map Name</span>
187 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => ( 264 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
188 <option value={e.id} key={i + 1}>{e.name}</option> 265 </div>
189 ))}</select> 266 <div className="flex place-items-end cursor-pointer">
190 } 267 <span>Portals</span>
191 </div> 268 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
192 <div id='profileboard-top'> 269 </div>
193 <span><span>Map Name</span><img src={SortIcon} alt="" /></span> 270 <div className="flex place-items-end cursor-pointer">
194 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span> 271 <span>WRΔ</span>
195 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span> 272 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
196 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span> 273 </div>
197 <span> </span> 274 <div className="flex place-items-end cursor-pointer">
198 <span><span>Rank</span><img src={SortIcon} alt="" /></span> 275 <span>Time</span>
199 <span><span>Date</span><img src={SortIcon} alt="" /></span> 276 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
200 <div id='page-number'> 277 </div>
201 <div> 278 <div></div>
202 <button onClick={() => { 279 <div className="flex place-items-end cursor-pointer">
203 if (pageNumber !== 1) { 280 <span>Rank</span>
204 setPageNumber(prevPageNumber => prevPageNumber - 1); 281 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
205 const records = document.querySelectorAll(".profileboard-record"); 282 </div>
206 records.forEach((r) => { 283 <div className="flex place-items-end cursor-pointer">
207 (r as HTMLInputElement).style.height = "44px"; 284 <span>Date</span>
208 }); 285 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
209 } 286 </div>
210 }} 287 <div className="flex items-center gap-[10px] justify-center">
211 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button> 288 <button
212 <span>{pageNumber}/{pageMax}</span> 289 className="w-8 h-8 border border-[#2b2e46] bg-[#2b2e46] rounded cursor-pointer flex items-center justify-center text-foreground transition-colors duration-100 hover:bg-[#202232] disabled:opacity-50 disabled:cursor-not-allowed"
213 <button onClick={() => { 290 onClick={() => setPageNumber(Math.max(1, pageNumber - 1))}
214 if (pageNumber !== pageMax) { 291 disabled={pageNumber === 1}
215 setPageNumber(prevPageNumber => prevPageNumber + 1); 292 >
216 const records = document.querySelectorAll(".profileboard-record"); 293
217 records.forEach((r) => { 294 </button>
218 (r as HTMLInputElement).style.height = "44px"; 295 <span className="text-sm text-foreground">{pageNumber}/{pageMax}</span>
219 }); 296 <button
220 } 297 className="w-8 h-8 border border-[#2b2e46] bg-[#2b2e46] rounded cursor-pointer flex items-center justify-center text-foreground transition-colors duration-100 hover:bg-[#202232] disabled:opacity-50 disabled:cursor-not-allowed"
221 }} 298 onClick={() => setPageNumber(Math.min(pageMax, pageNumber + 1))}
222 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button> 299 disabled={pageNumber === pageMax}
300 >
301
302 </button>
223 </div> 303 </div>
224 </div> 304 </div>
225 </div>
226 <hr />
227 <div id='profileboard-records'>
228
229 {game === "0"
230 ? (
231
232 user.records.sort((a, b) => a.map_id - b.map_id)
233 .map((r, index) => (
234 305
306 <div>
307 {game === "0" ? (
308 user.records
309 .sort((a, b) => a.map_id - b.map_id)
310 .map((record, index) =>
235 Math.ceil((index + 1) / 20) === pageNumber ? ( 311 Math.ceil((index + 1) / 20) === pageNumber ? (
236 <button className="profileboard-record" key={index}> 312 <div key={index} className="w-[calc(100%-40px)] mx-5 my-0 mt-[10px] h-11 rounded-[20px] pl-[40px] text-xl text-inherit font-inherit border-0 transition-colors duration-100 bg-[#2b2e46] grid grid-cols-[15%_15%_5%_15%_5%_15%_15%_15%] overflow-hidden whitespace-nowrap cursor-pointer hover:bg-[#202232]">
237 {r.scores.map((e, i) => (<> 313 <Link to={`/maps/${record.map_id}`} className="text-[#3c91e6] no-underline font-inherit flex place-items-center h-11 hover:underline">
238 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 314 {record.map_name}
239 315 </Link>
240 <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link> 316 <span className="flex place-items-center h-11">{record.scores[0]?.score_count || 'N/A'}</span>
241 317 <span className={`flex place-items-center h-11 ${record.scores[0]?.score_count - record.map_wr_count > 0 ? 'text-[#dc3545]' : ''}`}>
242 <span style={{ display: "grid" }}>{e.score_count}</span> 318 {record.scores[0]?.score_count - record.map_wr_count > 0
243 319 ? `+${record.scores[0].score_count - record.map_wr_count}`
244 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : `-`}</span> 320 : '–'}
245 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span> 321 </span>
246 <span> </span> 322 <span className="flex place-items-center h-11">{record.scores[0] ? ticks_to_time(record.scores[0].score_time) : 'N/A'}</span>
247 {i === 0 ? <span>#{r.placement}</span> : <span> </span>} 323 <span className="flex place-items-center h-11"></span>
248 <span>{e.date.split("T")[0]}</span> 324 <span className="flex place-items-center h-11 font-semibold">#{record.placement}</span>
249 <span style={{ flexDirection: "row-reverse" }}> 325 <span className="flex place-items-center h-11">{record.scores[0]?.date.split("T")[0] || 'N/A'}</span>
250 326 <div className="flex gap-[5px] justify-end flex-row-reverse place-items-center h-11">
251 <button onClick={() => { message("Demo Information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 327 <button
252 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 328 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
253 {i === 0 && r.scores.length > 1 ? <button onClick={() => { 329 onClick={() => message("Demo Information", `Demo ID: ${record.scores[0]?.demo_id}`)}
254 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 330 title="Demo Info"
255 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 331 >
256 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${r.scores.length * 46}px` : 332 <img src={ThreedotIcon} alt="Info" className="w-4 h-4" />
257 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px" 333 </button>
258 } 334 <button
259 }><img src={HistoryIcon} alt="history" /></button> : ""} 335 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
260 336 onClick={() => window.location.href = `/api/v1/demos?uuid=${record.scores[0]?.demo_id}`}
261 </span> 337 title="Download Demo"
262 </>))} 338 >
263 339 <img src={DownloadIcon} alt="Download" className="w-4 h-4" />
264 </button> 340 </button>
265 ) : "" 341 {record.scores.length > 1 && (
266 ))) : maps ? 342 <button className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]" title="View History">
267 343 <img src={HistoryIcon} alt="History" className="w-4 h-4" />
268 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id) 344 </button>
269 .map((r, index) => { 345 )}
270 if (Math.ceil((index + 1) / 20) === pageNumber) { 346 </div>
271 let record = user.records.find((e) => e.map_id === r.id); 347 </div>
272 return record === undefined ? ( 348 ) : null
273 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}> 349 )
274 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 350 ) : (
275 <span style={{ display: "grid" }}>N/A</span> 351 maps
276 <span style={{ display: "grid" }}>N/A</span> 352 ?.filter(map => !map.is_disabled)
277 <span>N/A</span> 353 .sort((a, b) => a.id - b.id)
278 <span> </span> 354 .map((map, index) => {
279 <span>N/A</span> 355 if (Math.ceil((index + 1) / 20) !== pageNumber) return null;
280 <span>N/A</span> 356
281 <span style={{ flexDirection: "row-reverse" }}></span> 357 const record = user.records.find(r => r.map_id === map.id);
282 </button> 358
283 ) : ( 359 return (
284 <button className="profileboard-record" key={index}> 360 <div key={index} className={`w-[calc(100%-40px)] mx-5 my-0 mt-[10px] h-11 rounded-[20px] pl-[40px] text-xl text-inherit font-inherit border-0 transition-colors duration-100 bg-[#2b2e46] grid grid-cols-[15%_15%_5%_15%_5%_15%_15%_15%] overflow-hidden whitespace-nowrap cursor-pointer hover:bg-[#202232] ${!record ? 'opacity-65' : ''}`}>
285 {record.scores.map((e, i) => (<> 361 <Link to={`/maps/${map.id}`} className="text-[#3c91e6] no-underline font-inherit flex place-items-center h-11 hover:underline">
286 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 362 {map.name}
287 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 363 </Link>
288 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span> 364 <span className="flex place-items-center h-11">{record?.scores[0]?.score_count || 'N/A'}</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> 365 <span className={`flex place-items-center h-11 ${record?.scores[0]?.score_count && record.scores[0].score_count - record.map_wr_count > 0 ? 'text-[#dc3545]' : ''}`}>
290 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span> 366 {record?.scores[0]?.score_count && record.scores[0].score_count - record.map_wr_count > 0
291 <span> </span> 367 ? `+${record.scores[0].score_count - record.map_wr_count}`
292 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>} 368 : '–'}
293 <span>{record!.scores[i].date.split("T")[0]}</span> 369 </span>
294 <span style={{ flexDirection: "row-reverse" }}> 370 <span className="flex place-items-center h-11">{record?.scores[0] ? ticks_to_time(record.scores[0].score_time) : 'N/A'}</span>
295 371 <span className="flex place-items-center h-11"></span>
296 <button onClick={() => { message("Demo Information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 372 <span className="flex place-items-center h-11 font-semibold">{record ? `#${record.placement}` : 'N/A'}</span>
297 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 373 <span className="flex place-items-center h-11">{record?.scores[0]?.date.split("T")[0] || 'N/A'}</span>
298 {i === 0 && record!.scores.length > 1 ? <button onClick={() => { 374 <div className="flex gap-[5px] justify-end flex-row-reverse place-items-center h-11">
299 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 375 {record?.scores[0] && (
300 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 376 <>
301 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${record!.scores.length * 46}px` : 377 <button
302 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px" 378 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
303 } 379 onClick={() => message("Demo Information", `Demo ID: ${record.scores[0].demo_id}`)}
304 }><img src={HistoryIcon} alt="history" /></button> : ""} 380 title="Demo Info"
305 381 >
306 </span> 382 <img src={ThreedotIcon} alt="Info" className="w-4 h-4" />
307 </>))} 383 </button>
308 </button> 384 <button
309 385 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
310 ) 386 onClick={() => window.location.href = `/api/v1/demos?uuid=${record.scores[0].demo_id}`}
311 } else { return null } 387 title="Download Demo"
312 }) : (<>{console.warn(maps)}</>)} 388 >
313 </div> 389 <img src={DownloadIcon} alt="Download" className="w-4 h-4" />
314 </section> 390 </button>
391 {record.scores.length > 1 && (
392 <button className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]" title="View History">
393 <img src={HistoryIcon} alt="History" className="w-4 h-4" />
394 </button>
395 )}
396 </>
397 )}
398 </div>
399 </div>
400 );
401 })
402 )}
403 </div>
404 </section>
405 )}
315 </main> 406 </main>
316 ); 407 );
317}; 408};