aboutsummaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
authorFifthWit <fifthwitbusiness@gmail.com>2025-08-14 15:44:50 -0500
committerFifthWit <fifthwitbusiness@gmail.com>2025-08-14 15:44:50 -0500
commitcfd377e29c0fa6f10c4d6bf3f507de4ca2f0b10a (patch)
tree6e30957cf5119a8ab83dd2c719e907e988619841 /frontend/src
parentSwitched to tailwind/vite (diff)
downloadlphub-cfd377e29c0fa6f10c4d6bf3f507de4ca2f0b10a.tar.gz
lphub-cfd377e29c0fa6f10c4d6bf3f507de4ca2f0b10a.tar.bz2
lphub-cfd377e29c0fa6f10c4d6bf3f507de4ca2f0b10a.zip
Mobile Design looking decent
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/App.css16
-rw-r--r--frontend/src/components/Leaderboards.tsx2
-rw-r--r--frontend/src/components/Summary.tsx10
-rw-r--r--frontend/src/pages/About.tsx4
-rw-r--r--frontend/src/pages/Games.tsx2
-rw-r--r--frontend/src/pages/Homepage.tsx2
-rw-r--r--frontend/src/pages/Maplist.tsx271
-rw-r--r--frontend/src/pages/Maps.tsx39
-rw-r--r--frontend/src/pages/Rankings.tsx32
-rw-r--r--frontend/src/pages/Rules.tsx2
-rw-r--r--frontend/src/pages/User.tsx627
11 files changed, 410 insertions, 597 deletions
diff --git a/frontend/src/App.css b/frontend/src/App.css
index a4c058b..464b759 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -49,22 +49,6 @@
49 --font-barlow-semicondensed-semibold: 'BarlowSemiCondensed-SemiBold'; 49 --font-barlow-semicondensed-semibold: 'BarlowSemiCondensed-SemiBold';
50} 50}
51 51
52main {
53 overflow: auto;
54 overflow-x: hidden;
55 position: relative;
56
57 width: calc(100% - 380px);
58 height: 100vh;
59 left: 350px;
60
61 padding-right: 30px;
62
63 font-size: 40px;
64 font-family: var(--font-barlow-semicondensed-regular);
65 color: var(--color-text);
66
67}
68 52
69a { 53a {
70 color: inherit; 54 color: inherit;
diff --git a/frontend/src/components/Leaderboards.tsx b/frontend/src/components/Leaderboards.tsx
index 99481a2..1de9b08 100644
--- a/frontend/src/components/Leaderboards.tsx
+++ b/frontend/src/components/Leaderboards.tsx
@@ -51,7 +51,7 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
51 } 51 }
52 52
53 return ( 53 return (
54 <div> 54 <div className="text-foreground">
55 {MessageDialogComponent} 55 {MessageDialogComponent}
56 <section id="section6" className="summary2"> 56 <section id="section6" className="summary2">
57 <div 57 <div
diff --git a/frontend/src/components/Summary.tsx b/frontend/src/components/Summary.tsx
index cdecf30..686652a 100644
--- a/frontend/src/components/Summary.tsx
+++ b/frontend/src/components/Summary.tsx
@@ -81,7 +81,7 @@ const Summary: React.FC<SummaryProps> = ({
81 81
82 return ( 82 return (
83 <> 83 <>
84 <section id="section3" className="summary1"> 84 <section id="section3" className="summary1 text-foreground">
85 <div 85 <div
86 id="category" 86 id="category"
87 style={data.map.image === "" ? { backgroundColor: "#202232" } : {}} 87 style={data.map.image === "" ? { backgroundColor: "#202232" } : {}}
@@ -174,8 +174,8 @@ const Summary: React.FC<SummaryProps> = ({
174 </section> 174 </section>
175 <section id="section4" className="summary1"> 175 <section id="section4" className="summary1">
176 <div id="difficulty"> 176 <div id="difficulty">
177 <span>Difficulty</span> 177 <span className="">Difficulty</span>
178 {data.summary.routes[selectedRun].rating === 0 && <span>N/A</span>} 178 {data.summary.routes[selectedRun].rating === 0 && <span className="text-foreground">N/A</span>}
179 {data.summary.routes[selectedRun].rating === 1 && ( 179 {data.summary.routes[selectedRun].rating === 1 && (
180 <span style={{ color: "lime" }}>Very easy</span> 180 <span style={{ color: "lime" }}>Very easy</span>
181 )} 181 )}
@@ -255,9 +255,9 @@ const Summary: React.FC<SummaryProps> = ({
255 ) : ( 255 ) : (
256 "" 256 ""
257 )} 257 )}
258 <h3>Route Description</h3> 258 <h3 className="font-semibold">Route Description</h3>
259 <span id="description-text"> 259 <span id="description-text">
260 <ReactMarkdown> 260 <ReactMarkdown className="text-foreground">
261 {data.summary.routes[selectedRun].description} 261 {data.summary.routes[selectedRun].description}
262 </ReactMarkdown> 262 </ReactMarkdown>
263 </span> 263 </span>
diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx
index a5bb291..7802d75 100644
--- a/frontend/src/pages/About.tsx
+++ b/frontend/src/pages/About.tsx
@@ -24,11 +24,11 @@ const About: React.FC = () => {
24 }, []); 24 }, []);
25 25
26 return ( 26 return (
27 <div className="p-8 text-foreground font-[--font-barlow-semicondensed-regular] prose prose-invert max-w-none"> 27 <div className="ml-16 p-8 text-foreground font-[--font-barlow-semicondensed-regular] prose prose-invert max-w-none">
28 <Helmet> 28 <Helmet>
29 <title>LPHUB | About</title> 29 <title>LPHUB | About</title>
30 </Helmet> 30 </Helmet>
31 <ReactMarkdown>{aboutText}</ReactMarkdown> 31 <ReactMarkdown className={"overflow-auto"}>{aboutText}</ReactMarkdown>
32 </div> 32 </div>
33 ); 33 );
34}; 34};
diff --git a/frontend/src/pages/Games.tsx b/frontend/src/pages/Games.tsx
index 1ef0f57..8587635 100644
--- a/frontend/src/pages/Games.tsx
+++ b/frontend/src/pages/Games.tsx
@@ -10,7 +10,7 @@ interface GamesProps {
10 10
11const Games: React.FC<GamesProps> = ({ games }) => { 11const Games: React.FC<GamesProps> = ({ games }) => {
12 return ( 12 return (
13 <div className="ml-10 min-h-screen w-[calc(100%-320px)] text-foreground font-[--font-barlow-semicondensed-regular] overflow-y-auto scrollbar-thin"> 13 <div className="ml-20 min-h-screen text-foreground font-[--font-barlow-semicondensed-regular] overflow-y-auto scrollbar-thin">
14 <Helmet> 14 <Helmet>
15 <title>LPHUB | Games</title> 15 <title>LPHUB | Games</title>
16 </Helmet> 16 </Helmet>
diff --git a/frontend/src/pages/Homepage.tsx b/frontend/src/pages/Homepage.tsx
index 2d16b8d..b4ac3b0 100644
--- a/frontend/src/pages/Homepage.tsx
+++ b/frontend/src/pages/Homepage.tsx
@@ -3,7 +3,7 @@ import { Helmet } from "react-helmet";
3 3
4const Homepage: React.FC = () => { 4const Homepage: React.FC = () => {
5 return ( 5 return (
6 <main className="text-foreground font-[--font-barlow-semicondensed-regular]"> 6 <main className="ml-12 relative left-0 w-fullmin-h-screen p-4 sm:p-8 text-foreground font-[--font-barlow-semicondensed-regular]">
7 <Helmet> 7 <Helmet>
8 <title>LPHUB | Homepage</title> 8 <title>LPHUB | Homepage</title>
9 </Helmet> 9 </Helmet>
diff --git a/frontend/src/pages/Maplist.tsx b/frontend/src/pages/Maplist.tsx
index 8343129..2f0491e 100644
--- a/frontend/src/pages/Maplist.tsx
+++ b/frontend/src/pages/Maplist.tsx
@@ -87,161 +87,160 @@ const Maplist: React.FC = () => {
87 }, [gameChapters, location.search]); 87 }, [gameChapters, location.search]);
88 88
89 return ( 89 return (
90 <main> 90 <main className="*:text-foreground w-[calc(100vw-80px)] relative left-0 ml-20 min-h-screen p-4 sm:p-8">
91 <Helmet> 91 <Helmet>
92 <title>LPHUB | Maplist</title> 92 <title>LPHUB | Maplist</title>
93 </Helmet> 93 </Helmet>
94 94
95 <section className="mt-5"> 95 <section className="mt-5">
96 <Link to="/games"> 96 <Link to="/games">
97 <button className="nav-button rounded-[20px] h-10 bg-surface border-0 text-foreground text-lg font-[--font-barlow-semicondensed-regular] transition-colors duration-100 hover:bg-surface2 flex items-center px-2"> 97 <button className="nav-button rounded-[20px] h-10 bg-surface border-0 text-foreground text-lg font-[--font-barlow-semicondensed-regular] transition-colors duration-100 hover:bg-surface2 flex items-center px-2">
98 <i className="triangle mr-2"></i> 98 <i className="triangle mr-2"></i>
99 <span className="px-2">Games List</span> 99 <span className="px-2">Games List</span>
100 </button> 100 </button>
101 </Link> 101 </Link>
102 </section> 102 </section>
103 103
104 {load ? ( 104 {load ? (
105 <div></div> 105 <div></div>
106 ) : ( 106 ) : (
107 <section> 107 <section>
108 <h1 className="font-[--font-barlow-condensed-bold] text-6xl my-0 text-foreground"> 108 <h1 className="font-[--font-barlow-condensed-bold] text-3xl sm:text-6xl my-0 text-foreground">
109 {game?.name} 109 {game?.name}
110 </h1> 110 </h1>
111
112 <div
113 className="text-center rounded-3xl overflow-hidden bg-cover bg-[25%] mt-3 relative"
114 style={{ backgroundImage: `url(${game?.image})` }}
115 >
116 <div className="backdrop-blur-sm flex flex-col w-full">
117 <div className="h-full flex flex-col justify-center items-center py-6">
118 <h2 className="my-5 font-[--font-barlow-semicondensed-semibold] text-4xl sm:text-8xl text-foreground">
119 {
120 game?.category_portals.find(
121 obj => obj.category.id === catNum + 1
122 )?.portal_count
123 }
124 </h2>
125 <h3 className="font-[--font-barlow-semicondensed-regular] mx-2.5 text-2xl sm:text-4xl my-0 text-foreground">
126 portals
127 </h3>
128 </div>
129
130 <div className="flex h-12 bg-surface gap-0.5">
131 {game?.category_portals.map((cat, index) => (
132 <button
133 key={index}
134 className={`border-0 text-foreground font-[--font-barlow-semicondensed-regular] text-sm sm:text-xl cursor-pointer transition-all duration-100 w-full ${
135 currentlySelected === cat.category.id ||
136 (cat.category.id - 1 === catNum && !hasClicked)
137 ? "bg-surface"
138 : "bg-surface1 hover:bg-surface"
139 }`}
140 onClick={() => {
141 setCatNum(cat.category.id - 1);
142 _update_currently_selected(cat.category.id);
143 }}
144 >
145 <span className="truncate">{cat.category.name}</span>
146 </button>
147 ))}
148 </div>
149 </div>
150 </div>
111 151
152 <div>
153 <section>
154 <div>
155 <span className="text-lg sm:text-lg translate-y-1.5 block mt-2.5 text-foreground">
156 {curChapter?.chapter.name.split(" - ")[0]}
157 </span>
158 </div>
159 <div
160 onClick={_handle_dropdown_click}
161 className="cursor-pointer select-none flex w-fit items-center"
162 >
163 <span className="text-foreground text-base sm:text-2xl">
164 {curChapter?.chapter.name.split(" - ")[1]}
165 </span>
166 <i className="triangle translate-x-1.5 translate-y-2 -rotate-90"></i>
167 </div>
168 \
112 <div 169 <div
113 className="text-center rounded-3xl overflow-hidden bg-cover bg-[25%] mt-3 relative" 170 className={`absolute z-[1000] bg-surface1 rounded-2xl overflow-hidden p-1 animate-in fade-in duration-100 ${
114 style={{ backgroundImage: `url(${game?.image})` }} 171 dropdownActive === "none" ? "hidden" : "block"
172 }`}
115 > 173 >
116 <div className="backdrop-blur-sm flex flex-col w-full"> 174 {gameChapters?.chapters.map((chapter, i) => {
117 <div className="h-full flex flex-col justify-center items-center"> 175 return (
118 <h2 className="my-5 font-[--font-barlow-semicondensed-semibold] text-8xl text-foreground"> 176 <div
119 { 177 key={i}
120 game?.category_portals.find( 178 className="cursor-pointer text-base sm:text-xl rounded-[2000px] p-1 hover:bg-surface text-foreground"
121 obj => obj.category.id === catNum + 1 179 onClick={() => {
122 )?.portal_count 180 _fetch_chapters(chapter.id.toString());
123 } 181 _handle_dropdown_click();
124 </h2> 182 }}
125 <h3 className="font-[--font-barlow-semicondensed-regular] mx-2.5 text-4xl my-0 text-foreground"> 183 >
126 portals 184 {chapter.name}
127 </h3>
128 </div>
129
130 <div className="flex h-12 bg-surface gap-0.5">
131 {game?.category_portals.map((cat, index) => (
132 <button
133 key={index}
134 className={`border-0 text-foreground font-[--font-barlow-semicondensed-regular] text-xl cursor-pointer transition-all duration-100 w-full ${
135 currentlySelected === cat.category.id ||
136 (cat.category.id - 1 === catNum && !hasClicked)
137 ? "bg-surface"
138 : "bg-surface1 hover:bg-surface"
139 }`}
140 onClick={() => {
141 setCatNum(cat.category.id - 1);
142 _update_currently_selected(cat.category.id);
143 }}
144 >
145 <span>{cat.category.name}</span>
146 </button>
147 ))}
148 </div>
149 </div> 185 </div>
186 );
187 })}
150 </div> 188 </div>
189 </section>
151 190
152 <div> 191 <section className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5 my-5">
153 <section> 192 {curChapter?.maps.map((map, i) => {
154 <div> 193 return (
155 <span className="text-lg translate-y-1.5 block mt-2.5 text-foreground"> 194 <div key={i} className="bg-surface rounded-3xl overflow-hidden">
156 {curChapter?.chapter.name.split(" - ")[0]} 195 <Link to={`/maps/${map.id}`}>
157 </span> 196 <span className="text-center text-base sm:text-xl w-full block my-1.5 text-foreground truncate">
158 </div> 197 {map.name}
159 <div 198 </span>
160 onClick={_handle_dropdown_click} 199 <div
161 className="cursor-pointer select-none flex w-fit items-center" 200 className="flex h-40 sm:h-48 bg-cover relative"
201 style={{ backgroundImage: `url(${map.image})` }}
162 > 202 >
163 <span className="text-foreground text-2xl"> 203 <div className="backdrop-blur-sm w-full flex items-center justify-center">
164 {curChapter?.chapter.name.split(" - ")[1]} 204 <span className="text-2xl sm:text-4xl font-[--font-barlow-semicondensed-semibold] text-white mr-1.5">
205 {map.is_disabled
206 ? map.category_portals[0].portal_count
207 : map.category_portals.find(
208 obj => obj.category.id === catNum + 1
209 )?.portal_count}
210 </span>
211 <span className="text-2xl sm:text-4xl font-[--font-barlow-semicondensed-regular] text-white">
212 portals
165 </span> 213 </span>
166 <i className="triangle translate-x-1.5 translate-y-2 -rotate-90"></i>
167 </div> 214 </div>
168 \
169 <div
170 className={`absolute z-[1000] bg-surface1 rounded-2xl overflow-hidden p-1 animate-in fade-in duration-100 ${
171 dropdownActive === "none" ? "hidden" : "block"
172 }`}
173 >
174 {gameChapters?.chapters.map((chapter, i) => {
175 return (
176 <div
177 key={i}
178 className="cursor-pointer text-xl rounded-[2000px] p-1 hover:bg-surface text-foreground"
179 onClick={() => {
180 _fetch_chapters(chapter.id.toString());
181 _handle_dropdown_click();
182 }}
183 >
184 {chapter.name}
185 </div>
186 );
187 })}
188 </div> 215 </div>
189 </section> 216
190 217 <div className="flex mx-2.5 my-4">
191 <section className="grid grid-cols-4 gap-5 my-5"> 218 <div className="flex w-full items-center justify-center gap-1.5 rounded-[2000px] ml-0.5 translate-y-px">
192 {curChapter?.maps.map((map, i) => { 219 {[1, 2, 3, 4, 5].map((point) => (
193 return ( 220 <div
194 <div key={i} className="bg-surface rounded-3xl overflow-hidden"> 221 key={point}
195 <Link to={`/maps/${map.id}`}> 222 className={`flex h-0.5 w-full rounded-3xl ${
196 <span className="text-center text-xl w-full block my-1.5 text-foreground"> 223 point <= (map.difficulty + 1)
197 {map.name} 224 ? map.difficulty === 0
198 </span> 225 ? "bg-green-500"
199 <div 226 : map.difficulty === 1 || map.difficulty === 2
200 className="flex h-48 bg-cover relative" 227 ? "bg-lime-500"
201 style={{ backgroundImage: `url(${map.image})` }} 228 : map.difficulty === 3
202 > 229 ? "bg-red-400"
203 <div className="backdrop-blur-sm w-full flex items-center justify-center"> 230 : "bg-red-600"
204 <span className="text-4xl font-[--font-barlow-semicondensed-semibold] text-white mr-1.5"> 231 : "bg-surface1"
205 {map.is_disabled 232 }`}
206 ? map.category_portals[0].portal_count 233 />
207 : map.category_portals.find( 234 ))}
208 obj => obj.category.id === catNum + 1 235 </div>
209 )?.portal_count} 236 </div>
210 </span> 237 </Link>
211 <span className="text-4xl font-[--font-barlow-semicondensed-regular] text-white"> 238 </div>
212 portals 239 );
213 </span> 240 })}
214 </div>
215 </div>
216
217 {/* Difficulty rating */}
218 <div className="flex mx-2.5 my-4">
219 <div className="flex w-full items-center justify-center gap-1.5 rounded-[2000px] ml-0.5 translate-y-px">
220 {[1, 2, 3, 4, 5].map((point) => (
221 <div
222 key={point}
223 className={`flex h-0.5 w-full rounded-3xl ${
224 point <= (map.difficulty + 1)
225 ? map.difficulty === 0
226 ? "bg-green-500"
227 : map.difficulty === 1 || map.difficulty === 2
228 ? "bg-lime-500"
229 : map.difficulty === 3
230 ? "bg-red-400"
231 : "bg-red-600"
232 : "bg-surface1"
233 }`}
234 />
235 ))}
236 </div>
237 </div>
238 </Link>
239 </div>
240 );
241 })}
242 </section>
243 </div>
244 </section> 241 </section>
242 </div>
243 </section>
245 )} 244 )}
246 </main> 245 </main>
247 ); 246 );
diff --git a/frontend/src/pages/Maps.tsx b/frontend/src/pages/Maps.tsx
index 75753ac..50fe03b 100644
--- a/frontend/src/pages/Maps.tsx
+++ b/frontend/src/pages/Maps.tsx
@@ -64,37 +64,36 @@ const Maps: React.FC<MapProps> = ({ token, isModerator }) => {
64 // loading placeholder 64 // loading placeholder
65 return ( 65 return (
66 <> 66 <>
67 <main> 67 <main className="*:text-foreground relative left-0 w-[calc(100%-20rem)] min-h-screen p-4 sm:p-8">
68 <section id="section1" className="summary1"> 68 <section id="section1" className="summary1">
69 <div> 69 <div>
70 <Link to="/games"> 70 <Link to="/games">
71 <button 71 <button
72 className="nav-button" 72 className="nav-button rounded-[20px] h-10 bg-surface border-0 text-foreground text-lg font-[--font-barlow-semicondensed-regular] transition-colors duration-100 hover:bg-surface2 flex items-center px-2"
73 style={{ borderRadius: "20px 20px 20px 20px" }}
74 > 73 >
75 <i className="triangle"></i> 74 <i className="triangle"></i>
76 <span>Games List</span> 75 <span className="px-2">Games List</span>
77 </button> 76 </button>
78 </Link> 77 </Link>
79 </div> 78 </div>
80 </section> 79 </section>
81 80
82 <section id="section2" className="summary1"> 81 <section id="section2" className="summary1 mt-4 flex gap-2 flex-wrap">
83 <button className="nav-button"> 82 <button className="nav-button">
84 <img src={PortalIcon} alt="" className="w-6 h-6" /> 83 <img src={PortalIcon} alt="" className="w-5 h-5 sm:w-6 sm:h-6" />
85 <span>Summary</span> 84 <span>Summary</span>
86 </button> 85 </button>
87 <button className="nav-button"> 86 <button className="nav-button">
88 <img src={FlagIcon} alt="" className="w-6 h-6" /> 87 <img src={FlagIcon} alt="" className="w-5 h-5 sm:w-6 sm:h-6" />
89 <span>Leaderboards</span> 88 <span>Leaderboards</span>
90 </button> 89 </button>
91 <button className="nav-button"> 90 <button className="nav-button">
92 <img src={ChatIcon} alt="" className="w-6 h-6" /> 91 <img src={ChatIcon} alt="" className="w-5 h-5 sm:w-6 sm:h-6" />
93 <span>Discussions</span> 92 <span>Discussions</span>
94 </button> 93 </button>
95 </section> 94 </section>
96 95
97 <section id="section6" className="summary2" /> 96 <section id="section6" className="summary2 mt-4" />
98 </main> 97 </main>
99 </> 98 </>
100 ); 99 );
@@ -118,47 +117,45 @@ const Maps: React.FC<MapProps> = ({ token, isModerator }) => {
118 <div id="background-image"> 117 <div id="background-image">
119 <img src={mapSummaryData.map.image} alt="" /> 118 <img src={mapSummaryData.map.image} alt="" />
120 </div> 119 </div>
121 <main> 120 <main className="relative left-0 w-full sm:ml-80 sm:w-[calc(100%-20rem)] min-h-screen max-h-screen overflow-y-auto p-4 sm:p-8 scrollbar-thin scrollbar-track-surface scrollbar-thumb-muted hover:scrollbar-thumb-surface1">
122 <section id="section1" className="summary1"> 121 <section id="section1" className="summary1">
123 <div> 122 <div>
124 <Link to="/games"> 123 <Link to="/games">
125 <button 124 <button
126 className="nav-button" 125 className="nav-button rounded-[20px] h-10 bg-surface border-0 text-foreground text-lg font-[--font-barlow-semicondensed-regular] transition-colors duration-100 hover:bg-surface2 flex items-center px-2"
127 style={{ borderRadius: "20px 0px 0px 20px" }}
128 > 126 >
129 <i className="triangle"></i> 127 <i className="triangle"></i>
130 <span>Games List</span> 128 <span className="px-2">Games List</span>
131 </button> 129 </button>
132 </Link> 130 </Link>
133 <Link 131 <Link
134 to={`/games/${mapSummaryData.map.is_coop ? "2" : "1"}?chapter=${mapSummaryData.map.chapter_name.split(" ")[1]}`} 132 to={`/games/${mapSummaryData.map.is_coop ? "2" : "1"}?chapter=${mapSummaryData.map.chapter_name.split(" ")[1]}`}
135 > 133 >
136 <button 134 <button
137 className="nav-button" 135 className="nav-button ml-2"
138 style={{ borderRadius: "0px 20px 20px 0px", marginLeft: "2px" }}
139 > 136 >
140 <i className="triangle"></i> 137 <i className="triangle"></i>
141 <span>{mapSummaryData.map.chapter_name}</span> 138 <span className="px-2">{mapSummaryData.map.chapter_name}</span>
142 </button> 139 </button>
143 </Link> 140 </Link>
144 <br /> 141 <br />
145 <span> 142 <span className="block mt-2 text-lg sm:text-xl text-foreground">
146 <b>{mapSummaryData.map.map_name}</b> 143 <b>{mapSummaryData.map.map_name}</b>
147 </span> 144 </span>
148 </div> 145 </div>
149 </section> 146 </section>
150 147
151 <section id="section2" className="summary1"> 148 <section id="section2" className="summary1 mt-4 flex gap-2 flex-wrap">
152 <button className="nav-button" onClick={() => setNavState(0)}> 149 <button className="nav-button" onClick={() => setNavState(0)}>
153 <img src={PortalIcon} alt="" className="w-6 h-6" /> 150 <img src={PortalIcon} alt="" className="w-5 h-5 sm:w-6 sm:h-6" />
154 <span>Summary</span> 151 <span>Summary</span>
155 </button> 152 </button>
156 <button className="nav-button" onClick={() => setNavState(1)}> 153 <button className="nav-button" onClick={() => setNavState(1)}>
157 <img src={FlagIcon} alt="" className="w-6 h-6" /> 154 <img src={FlagIcon} alt="" className="w-5 h-5 sm:w-6 sm:h-6" />
158 <span>Leaderboards</span> 155 <span>Leaderboards</span>
159 </button> 156 </button>
160 <button className="nav-button" onClick={() => setNavState(2)}> 157 <button className="nav-button" onClick={() => setNavState(2)}>
161 <img src={ChatIcon} alt="" className="w-6 h-6" /> 158 <img src={ChatIcon} alt="" className="w-5 h-5 sm:w-6 sm:h-6" />
162 <span>Discussions</span> 159 <span>Discussions</span>
163 </button> 160 </button>
164 </section> 161 </section>
diff --git a/frontend/src/pages/Rankings.tsx b/frontend/src/pages/Rankings.tsx
index 275f9d0..dec0e17 100644
--- a/frontend/src/pages/Rankings.tsx
+++ b/frontend/src/pages/Rankings.tsx
@@ -12,6 +12,17 @@ import { API } from "@api/Api";
12 12
13import "@css/Rankings.css"; 13import "@css/Rankings.css";
14 14
15enum LeaderboardTypes {
16 official,
17 unofficial,
18}
19
20enum RankingCategories {
21 rankings_overall,
22 rankings_multiplayer,
23 rankings_singleplayer,
24}
25
15const Rankings: React.FC = () => { 26const Rankings: React.FC = () => {
16 const [leaderboardData, setLeaderboardData] = React.useState< 27 const [leaderboardData, setLeaderboardData] = React.useState<
17 Ranking | SteamRanking 28 Ranking | SteamRanking
@@ -19,20 +30,11 @@ const Rankings: React.FC = () => {
19 const [currentLeaderboard, setCurrentLeaderboard] = React.useState< 30 const [currentLeaderboard, setCurrentLeaderboard] = React.useState<
20 RankingType[] | SteamRankingType[] 31 RankingType[] | SteamRankingType[]
21 >(); 32 >();
22 enum LeaderboardTypes {
23 official,
24 unofficial,
25 }
26 const [currentRankingType, setCurrentRankingType] = 33 const [currentRankingType, setCurrentRankingType] =
27 React.useState<LeaderboardTypes>(LeaderboardTypes.official); 34 React.useState<LeaderboardTypes>(LeaderboardTypes.official);
28 35
29 const [leaderboardLoad, setLeaderboardLoad] = React.useState<boolean>(false); 36 const [leaderboardLoad, setLeaderboardLoad] = React.useState<boolean>(false);
30 37
31 enum RankingCategories {
32 rankings_overall,
33 rankings_multiplayer,
34 rankings_singleplayer,
35 }
36 const [currentLeaderboardType, setCurrentLeaderboardType] = 38 const [currentLeaderboardType, setCurrentLeaderboardType] =
37 React.useState<RankingCategories>(RankingCategories.rankings_singleplayer); 39 React.useState<RankingCategories>(RankingCategories.rankings_singleplayer);
38 const [load, setLoad] = React.useState<boolean>(false); 40 const [load, setLoad] = React.useState<boolean>(false);
@@ -100,18 +102,10 @@ const Rankings: React.FC = () => {
100 102
101 useEffect(() => { 103 useEffect(() => {
102 _fetch_rankings(); 104 _fetch_rankings();
103 if (load) { 105 }, [_fetch_rankings]);
104 _set_current_leaderboard(RankingCategories.rankings_singleplayer);
105 }
106 }, [
107 load,
108 RankingCategories.rankings_singleplayer,
109 _fetch_rankings,
110 _set_current_leaderboard,
111 ]);
112 106
113 return ( 107 return (
114 <main> 108 <main className="*:text-foreground">
115 <Helmet> 109 <Helmet>
116 <title>LPHUB | Rankings</title> 110 <title>LPHUB | Rankings</title>
117 </Helmet> 111 </Helmet>
diff --git a/frontend/src/pages/Rules.tsx b/frontend/src/pages/Rules.tsx
index 7cdc08b..9c7885c 100644
--- a/frontend/src/pages/Rules.tsx
+++ b/frontend/src/pages/Rules.tsx
@@ -25,7 +25,7 @@ const Rules: React.FC = () => {
25 }, []); 25 }, []);
26 26
27 return ( 27 return (
28 <main className="p-8 text-foreground font-[--font-barlow-semicondensed-regular] prose prose-invert max-w-none"> 28 <main className="ml-16 p-8 text-foreground font-[--font-barlow-semicondensed-regular] prose prose-invert max-w-none">
29 <Helmet> 29 <Helmet>
30 <title>LPHUB | Rules</title> 30 <title>LPHUB | Rules</title>
31 </Helmet> 31 </Helmet>
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx
index 4b8a456..8c699b1 100644
--- a/frontend/src/pages/User.tsx
+++ b/frontend/src/pages/User.tsx
@@ -97,473 +97,312 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
97 }, [user, game, chapter, location, _get_game_maps]); 97 }, [user, game, chapter, location, _get_game_maps]);
98 98
99 if (!user) { 99 if (!user) {
100 return <></>; 100 return (
101 <div className="flex justify-center items-center h-[50vh] text-lg text-foreground">
102 Loading...
103 </div>
104 );
101 } 105 }
102 106
103 return ( 107 return (
104 <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">
105 <Helmet> 109 <Helmet>
106 <title>LPHUB | {user.user_name}</title> 110 <title>LPHUB | {user.user_name}</title>
107 <meta name="description" content={user.user_name} /> 111 <meta name="description" content={user.user_name} />
108 </Helmet> 112 </Helmet>
113
109 {MessageDialogComponent} 114 {MessageDialogComponent}
110 <section id="section1" className="profile"> 115
111 <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">
112 <img src={user.avatar_link} alt="profile-image"></img> 117 <div className="grid grid-cols-[200px_1fr_auto] items-center gap-[25px] mb-[25px]">
113 </div> 118 <img
114 <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 />
115 <div> 123 <div>
116 <div>{user.user_name}</div> 124 <h1 className="m-0 mb-[10px] text-[50px] font-bold text-white font-[--font-barlow-semicondensed-regular]">
117 <div> 125 {user.user_name}
118 {user.country_code === "XX" ? ( 126 </h1>
119 "" 127 {user.country_code !== "XX" && (
120 ) : ( 128 <div className="flex items-center gap-3 mb-[15px]">
121 <img 129 <img
122 src={`https://flagcdn.com/w80/${user.country_code.toLowerCase()}.jpg`} 130 src={`https://flagcdn.com/w80/${user.country_code.toLowerCase()}.jpg`}
123 alt={user.country_code} 131 alt={user.country_code}
132 className="w-6 h-4 rounded-[10px]"
124 /> 133 />
125 )} 134 <span>{user.country_code}</span>
126 </div> 135 </div>
127 <div> 136 )}
128 {user.titles.map(e => ( 137 <div className="flex flex-wrap gap-2">
138 {user.titles.map((title, index) => (
129 <span 139 <span
130 className="titles" 140 key={index}
131 style={{ backgroundColor: `#${e.color}` }} 141 className="py-[6px] px-5 pt-[6px] rounded-[10px] text-lg font-normal text-white"
142 style={{ backgroundColor: `#${title.color}` }}
132 > 143 >
133 {e.name} 144 {title.name}
134 </span> 145 </span>
135 ))} 146 ))}
136 </div> 147 </div>
137 </div> 148 </div>
138 <div> 149 <div className="flex gap-[15px] items-center pr-[10px]">
139 {user.links.steam === "-" ? ( 150 {user.links.steam !== "-" && (
140 "" 151 <a href={user.links.steam} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
141 ) : ( 152 <img src={SteamIcon} alt="Steam" className="h-[50px] px-[5px] scale-90 brightness-200" />
142 <a href={user.links.steam}>
143 <img src={SteamIcon} alt="Steam" />
144 </a> 153 </a>
145 )} 154 )}
146 {user.links.twitch === "-" ? ( 155 {user.links.twitch !== "-" && (
147 "" 156 <a href={user.links.twitch} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
148 ) : ( 157 <img src={TwitchIcon} alt="Twitch" className="h-[50px] px-[5px] scale-90 brightness-200" />
149 <a href={user.links.twitch}>
150 <img src={TwitchIcon} alt="Twitch" />
151 </a> 158 </a>
152 )} 159 )}
153 {user.links.youtube === "-" ? ( 160 {user.links.youtube !== "-" && (
154 "" 161 <a href={user.links.youtube} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
155 ) : ( 162 <img src={YouTubeIcon} alt="YouTube" className="h-[50px] px-[5px] scale-90 brightness-200" />
156 <a href={user.links.youtube}>
157 <img src={YouTubeIcon} alt="Youtube" />
158 </a> 163 </a>
159 )} 164 )}
160 {user.links.p2sr === "-" ? ( 165 {user.links.p2sr !== "-" && (
161 "" 166 <a href={user.links.p2sr} className="flex items-center justify-center transition-all duration-200 hover:-translate-y-0.5">
162 ) : ( 167 <img src={PortalIcon} alt="P2SR" className="h-[50px] px-[5px] scale-90 brightness-200" />
163 <a href={user.links.p2sr}>
164 <img src={PortalIcon} alt="P2SR" style={{ padding: "0" }} />
165 </a> 168 </a>
166 )} 169 )}
167 </div> 170 </div>
168 </div> 171 </div>
169 <div id="profile-bottom"> 172
170 <div> 173 <div className="grid grid-cols-3 gap-3 mt-24">
171 <span>Overall</span> 174 <div className="m-3 bg-[#2b2e46] rounded-[20px] p-5 text-center grid place-items-center grid-rows-[40%_50%]">
172 <span> 175 <div className="text-inherit text-lg">Overall</div>
173 {user.rankings.overall.rank === 0 176 <div className="text-white text-[40px]">
174 ? "N/A " 177 {user.rankings.overall.rank === 0 ? "N/A" : `#${user.rankings.overall.rank}`}
175 : "#" + user.rankings.overall.rank + " "} 178 </div>
176 <span> 179 <div className="text-white text-xl">
177 ({user.rankings.overall.completion_count}/ 180 {user.rankings.overall.completion_count}/{user.rankings.overall.completion_total}
178 {user.rankings.overall.completion_total}) 181 </div>
179 </span>
180 </span>
181 </div> 182 </div>
182 <div> 183 <div className="m-3 bg-[#2b2e46] rounded-[20px] p-5 text-center grid place-items-center grid-rows-[40%_50%]">
183 <span>Singleplayer</span> 184 <div className="text-inherit text-lg">Singleplayer</div>
184 <span> 185 <div className="text-white text-[40px]">
185 {user.rankings.singleplayer.rank === 0 186 {user.rankings.singleplayer.rank === 0 ? "N/A" : `#${user.rankings.singleplayer.rank}`}
186 ? "N/A " 187 </div>
187 : "#" + user.rankings.singleplayer.rank + " "} 188 <div className="text-white text-xl">
188 <span> 189 {user.rankings.singleplayer.completion_count}/{user.rankings.singleplayer.completion_total}
189 ({user.rankings.singleplayer.completion_count}/ 190 </div>
190 {user.rankings.singleplayer.completion_total})
191 </span>
192 </span>
193 </div> 191 </div>
194 <div> 192 <div className="m-3 bg-[#2b2e46] rounded-[20px] p-5 text-center grid place-items-center grid-rows-[40%_50%]">
195 <span>Cooperative</span> 193 <div className="text-inherit text-lg">Cooperative</div>
196 <span> 194 <div className="text-white text-[40px]">
197 {user.rankings.cooperative.rank === 0 195 {user.rankings.cooperative.rank === 0 ? "N/A" : `#${user.rankings.cooperative.rank}`}
198 ? "N/A " 196 </div>
199 : "#" + user.rankings.cooperative.rank + " "} 197 <div className="text-white text-xl">
200 <span> 198 {user.rankings.cooperative.completion_count}/{user.rankings.cooperative.completion_total}
201 ({user.rankings.cooperative.completion_count}/ 199 </div>
202 {user.rankings.cooperative.completion_total})
203 </span>
204 </span>
205 </div> 200 </div>
206 </div> 201 </div>
207 </section> 202 </section>
208 203
209 <section id="section2" className="profile"> 204 <section className="m-5 h-[60px] grid grid-cols-2">
210 <button onClick={() => setNavState(0)}> 205 <button
211 <img src={FlagIcon} alt="" /> 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] ${
212 &nbsp;Player Records 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> 213 </button>
214 <button onClick={() => setNavState(1)}> 214 <button
215 <img src={StatisticsIcon} alt="" /> 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 &nbsp;Statistics 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
217 </button> 222 </button>
218 </section> 223 </section>
219 224
220 <section id="section3" className="profile1"> 225 {navState === 0 && (
221 <div id="profileboard-nav"> 226 <section className="m-5 block bg-[#202232] rounded-3xl overflow-hidden">
222 {gameData === null ? ( 227 <div className="grid grid-cols-2 mx-5 my-5 mt-[10px] mb-5">
223 <select>error</select>
224 ) : (
225 <select 228 <select
226 id="select-game" 229 className="h-[50px] rounded-3xl text-center text-inherit font-inherit text-2xl border-0 bg-[#2b2e46] mr-[10px]"
227 onChange={() => { 230 value={game}
228 setGame( 231 onChange={(e) => {
229 (document.querySelector("#select-game") as HTMLInputElement) 232 setGame(e.target.value);
230 .value
231 );
232 setChapter("0"); 233 setChapter("0");
233 const chapterSelect = document.querySelector(
234 "#select-chapter"
235 ) as HTMLSelectElement;
236 if (chapterSelect) {
237 chapterSelect.value = "0";
238 }
239 }} 234 }}
240 > 235 >
241 <option value={0} key={0}> 236 <option value="0">All Games</option>
242 All Scores 237 {gameData?.map((g) => (
243 </option> 238 <option key={g.id} value={g.id}>
244 {gameData.map((e, i) => ( 239 {g.name}
245 <option value={e.id} key={i + 1}>
246 {e.name}
247 </option> 240 </option>
248 ))} 241 ))}
249 </select> 242 </select>
250 )}
251 243
252 {game === "0" ? (
253 <select disabled>
254 <option>All Chapters</option>
255 </select>
256 ) : chapterData === null ? (
257 <select></select>
258 ) : (
259 <select 244 <select
260 id="select-chapter" 245 className="h-[50px] rounded-3xl text-center text-inherit font-inherit text-2xl border-0 bg-[#2b2e46] mr-[10px] disabled:opacity-50"
261 onChange={() => 246 value={chapter}
262 setChapter( 247 onChange={(e) => setChapter(e.target.value)}
263 ( 248 disabled={game === "0"}
264 document.querySelector(
265 "#select-chapter"
266 ) as HTMLInputElement
267 ).value
268 )
269 }
270 > 249 >
271 <option value="0" key="0"> 250 <option value="0">All Chapters</option>
272 All Chapters 251 {chapterData?.chapters
273 </option> 252 .filter(c => !c.is_disabled)
274 {chapterData.chapters 253 .map((c) => (
275 .filter(e => e.is_disabled === false) 254 <option key={c.id} value={c.id}>
276 .map((e, i) => ( 255 {c.name}
277 <option value={e.id} key={i + 1}>
278 {e.name}
279 </option> 256 </option>
280 ))} 257 ))}
281 </select> 258 </select>
282 )} 259 </div>
283 </div> 260
284 <div id="profileboard-top"> 261 <div className="h-[34px] grid text-xl pl-[60px] mx-5 my-0 grid-cols-[15%_15%_5%_15%_5%_15%_15%_15%]">
285 <span> 262 <div className="flex place-items-end cursor-pointer">
286 <span>Map Name</span> 263 <span>Map Name</span>
287 <img src={SortIcon} alt="" /> 264 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
288 </span> 265 </div>
289 <span style={{ justifyContent: "center" }}> 266 <div className="flex place-items-end cursor-pointer">
290 <span>Portals</span> 267 <span>Portals</span>
291 <img src={SortIcon} alt="" /> 268 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
292 </span> 269 </div>
293 <span style={{ justifyContent: "center" }}> 270 <div className="flex place-items-end cursor-pointer">
294 <span>WRΔ </span> 271 <span>WRΔ</span>
295 <img src={SortIcon} alt="" /> 272 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
296 </span> 273 </div>
297 <span style={{ justifyContent: "center" }}> 274 <div className="flex place-items-end cursor-pointer">
298 <span>Time</span> 275 <span>Time</span>
299 <img src={SortIcon} alt="" /> 276 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
300 </span> 277 </div>
301 <span> </span> 278 <div></div>
302 <span> 279 <div className="flex place-items-end cursor-pointer">
303 <span>Rank</span> 280 <span>Rank</span>
304 <img src={SortIcon} alt="" /> 281 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
305 </span> 282 </div>
306 <span> 283 <div className="flex place-items-end cursor-pointer">
307 <span>Date</span> 284 <span>Date</span>
308 <img src={SortIcon} alt="" /> 285 <img src={SortIcon} alt="Sort" className="h-5 scale-[0.8]" />
309 </span> 286 </div>
310 <div id="page-number"> 287 <div className="flex items-center gap-[10px] justify-center">
311 <div>
312 <button 288 <button
313 onClick={() => { 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"
314 if (pageNumber !== 1) { 290 onClick={() => setPageNumber(Math.max(1, pageNumber - 1))}
315 setPageNumber(prevPageNumber => prevPageNumber - 1); 291 disabled={pageNumber === 1}
316 const records = document.querySelectorAll(
317 ".profileboard-record"
318 );
319 records.forEach(r => {
320 (r as HTMLInputElement).style.height = "44px";
321 });
322 }
323 }}
324 > 292 >
325 <i 293
326 className="triangle"
327 style={{ position: "relative", left: "-5px" }}
328 ></i>{" "}
329 </button> 294 </button>
330 <span> 295 <span className="text-sm text-foreground">{pageNumber}/{pageMax}</span>
331 {pageNumber}/{pageMax}
332 </span>
333 <button 296 <button
334 onClick={() => { 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"
335 if (pageNumber !== pageMax) { 298 onClick={() => setPageNumber(Math.min(pageMax, pageNumber + 1))}
336 setPageNumber(prevPageNumber => prevPageNumber + 1); 299 disabled={pageNumber === pageMax}
337 const records = document.querySelectorAll(
338 ".profileboard-record"
339 );
340 records.forEach(r => {
341 (r as HTMLInputElement).style.height = "44px";
342 });
343 }
344 }}
345 > 300 >
346 <i 301
347 className="triangle"
348 style={{
349 position: "relative",
350 left: "5px",
351 transform: "rotate(180deg)",
352 }}
353 ></i>{" "}
354 </button> 302 </button>
355 </div> 303 </div>
356 </div> 304 </div>
357 </div>
358 <hr />
359 <div id="profileboard-records">
360 {game === "0" ? (
361 user.records
362 .sort((a, b) => a.map_id - b.map_id)
363 .map((r, index) =>
364 Math.ceil((index + 1) / 20) === pageNumber ? (
365 <button className="profileboard-record" key={index}>
366 {r.scores.map((e, i) => (
367 <>
368 {i !== 0 ? (
369 <hr style={{ gridColumn: "1 / span 8" }} />
370 ) : (
371 ""
372 )}
373
374 <Link to={`/maps/${r.map_id}`}>
375 <span>{r.map_name}</span>
376 </Link>
377 305
378 <span style={{ display: "grid" }}>{e.score_count}</span> 306 <div>
379 307 {game === "0" ? (
380 <span style={{ display: "grid" }}> 308 user.records
381 {e.score_count - r.map_wr_count > 0 309 .sort((a, b) => a.map_id - b.map_id)
382 ? `+${e.score_count - r.map_wr_count}` 310 .map((record, index) =>
383 : `-`} 311 Math.ceil((index + 1) / 20) === pageNumber ? (
384 </span> 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]">
385 <span style={{ display: "grid" }}> 313 <Link to={`/maps/${record.map_id}`} className="text-[#3c91e6] no-underline font-inherit flex place-items-center h-11 hover:underline">
386 {ticks_to_time(e.score_time)} 314 {record.map_name}
387 </span> 315 </Link>
388 <span> </span> 316 <span className="flex place-items-center h-11">{record.scores[0]?.score_count || 'N/A'}</span>
389 {i === 0 ? <span>#{r.placement}</span> : <span> </span>} 317 <span className={`flex place-items-center h-11 ${record.scores[0]?.score_count - record.map_wr_count > 0 ? 'text-[#dc3545]' : ''}`}>
390 <span>{e.date.split("T")[0]}</span> 318 {record.scores[0]?.score_count - record.map_wr_count > 0
391 <span style={{ flexDirection: "row-reverse" }}> 319 ? `+${record.scores[0].score_count - record.map_wr_count}`
392 <button 320 : '–'}
393 onClick={() => { 321 </span>
394 message( 322 <span className="flex place-items-center h-11">{record.scores[0] ? ticks_to_time(record.scores[0].score_time) : 'N/A'}</span>
395 "Demo Information", 323 <span className="flex place-items-center h-11"></span>
396 `Demo ID: ${e.demo_id}` 324 <span className="flex place-items-center h-11 font-semibold">#{record.placement}</span>
397 ); 325 <span className="flex place-items-center h-11">{record.scores[0]?.date.split("T")[0] || 'N/A'}</span>
398 }} 326 <div className="flex gap-[5px] justify-end flex-row-reverse place-items-center h-11">
399 > 327 <button
400 <img src={ThreedotIcon} alt="demo_id" /> 328 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
401 </button> 329 onClick={() => message("Demo Information", `Demo ID: ${record.scores[0]?.demo_id}`)}
402 <button 330 title="Demo Info"
403 onClick={() => 331 >
404 (window.location.href = `/api/v1/demos?uuid=${e.demo_id}`) 332 <img src={ThreedotIcon} alt="Info" className="w-4 h-4" />
405 } 333 </button>
406 > 334 <button
407 <img src={DownloadIcon} alt="download" /> 335 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
336 onClick={() => window.location.href = `/api/v1/demos?uuid=${record.scores[0]?.demo_id}`}
337 title="Download Demo"
338 >
339 <img src={DownloadIcon} alt="Download" className="w-4 h-4" />
340 </button>
341 {record.scores.length > 1 && (
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">
343 <img src={HistoryIcon} alt="History" className="w-4 h-4" />
408 </button> 344 </button>
409 {i === 0 && r.scores.length > 1 ? ( 345 )}
410 <button 346 </div>
411 onClick={() => { 347 </div>
412 ( 348 ) : null
413 document.querySelectorAll(
414 ".profileboard-record"
415 )[index % 20] as HTMLInputElement
416 ).style.height === "44px" ||
417 (
418 document.querySelectorAll(
419 ".profileboard-record"
420 )[index % 20] as HTMLInputElement
421 ).style.height === ""
422 ? ((
423 document.querySelectorAll(
424 ".profileboard-record"
425 )[index % 20] as HTMLInputElement
426 ).style.height =
427 `${r.scores.length * 46}px`)
428 : ((
429 document.querySelectorAll(
430 ".profileboard-record"
431 )[index % 20] as HTMLInputElement
432 ).style.height = "44px");
433 }}
434 >
435 <img src={HistoryIcon} alt="history" />
436 </button>
437 ) : (
438 ""
439 )}
440 </span>
441 </>
442 ))}
443 </button>
444 ) : (
445 ""
446 ) 349 )
447 ) 350 ) : (
448 ) : maps ? ( 351 maps
449 maps 352 ?.filter(map => !map.is_disabled)
450 .filter(e => e.is_disabled === false) 353 .sort((a, b) => a.id - b.id)
451 .sort((a, b) => a.id - b.id) 354 .map((map, index) => {
452 .map((r, index) => { 355 if (Math.ceil((index + 1) / 20) !== pageNumber) return null;
453 if (Math.ceil((index + 1) / 20) === pageNumber) { 356
454 let record = user.records.find(e => e.map_id === r.id); 357 const record = user.records.find(r => r.map_id === map.id);
455 return record === undefined ? ( 358
456 <button 359 return (
457 className="profileboard-record" 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' : ''}`}>
458 key={index} 361 <Link to={`/maps/${map.id}`} className="text-[#3c91e6] no-underline font-inherit flex place-items-center h-11 hover:underline">
459 style={{ backgroundColor: "#1b1b20" }} 362 {map.name}
460 >
461 <Link to={`/maps/${r.id}`}>
462 <span>{r.name}</span>
463 </Link> 363 </Link>
464 <span style={{ display: "grid" }}>N/A</span> 364 <span className="flex place-items-center h-11">{record?.scores[0]?.score_count || 'N/A'}</span>
465 <span style={{ display: "grid" }}>N/A</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]' : ''}`}>
466 <span>N/A</span> 366 {record?.scores[0]?.score_count && record.scores[0].score_count - record.map_wr_count > 0
467 <span> </span> 367 ? `+${record.scores[0].score_count - record.map_wr_count}`
468 <span>N/A</span> 368 : '–'}
469 <span>N/A</span> 369 </span>
470 <span style={{ flexDirection: "row-reverse" }}></span> 370 <span className="flex place-items-center h-11">{record?.scores[0] ? ticks_to_time(record.scores[0].score_time) : 'N/A'}</span>
471 </button> 371 <span className="flex place-items-center h-11"></span>
472 ) : ( 372 <span className="flex place-items-center h-11 font-semibold">{record ? `#${record.placement}` : 'N/A'}</span>
473 <button className="profileboard-record" key={index}> 373 <span className="flex place-items-center h-11">{record?.scores[0]?.date.split("T")[0] || 'N/A'}</span>
474 {record.scores.map((e, i) => ( 374 <div className="flex gap-[5px] justify-end flex-row-reverse place-items-center h-11">
475 <> 375 {record?.scores[0] && (
476 {i !== 0 ? ( 376 <>
477 <hr style={{ gridColumn: "1 / span 8" }} />
478 ) : (
479 ""
480 )}
481 <Link to={`/maps/${r.id}`}>
482 <span>{r.name}</span>
483 </Link>
484 <span style={{ display: "grid" }}>
485 {record!.scores[i].score_count}
486 </span>
487 <span style={{ display: "grid" }}>
488 {record!.scores[i].score_count -
489 record!.map_wr_count >
490 0
491 ? `+${record!.scores[i].score_count - record!.map_wr_count}`
492 : `-`}
493 </span>
494 <span style={{ display: "grid" }}>
495 {ticks_to_time(record!.scores[i].score_time)}
496 </span>
497 <span> </span>
498 {i === 0 ? (
499 <span>#{record!.placement}</span>
500 ) : (
501 <span> </span>
502 )}
503 <span>{record!.scores[i].date.split("T")[0]}</span>
504 <span style={{ flexDirection: "row-reverse" }}>
505 <button 377 <button
506 onClick={() => { 378 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
507 message( 379 onClick={() => message("Demo Information", `Demo ID: ${record.scores[0].demo_id}`)}
508 "Demo Information", 380 title="Demo Info"
509 `Demo ID: ${e.demo_id}`
510 );
511 }}
512 > 381 >
513 <img src={ThreedotIcon} alt="demo_id" /> 382 <img src={ThreedotIcon} alt="Info" className="w-4 h-4" />
514 </button> 383 </button>
515 <button 384 <button
516 onClick={() => 385 className="bg-transparent border-0 cursor-pointer transition-colors duration-100 p-0.5 hover:bg-[rgba(32,34,50,0.5)]"
517 (window.location.href = `/api/v1/demos?uuid=${e.demo_id}`) 386 onClick={() => window.location.href = `/api/v1/demos?uuid=${record.scores[0].demo_id}`}
518 } 387 title="Download Demo"
519 > 388 >
520 <img src={DownloadIcon} alt="download" /> 389 <img src={DownloadIcon} alt="Download" className="w-4 h-4" />
521 </button> 390 </button>
522 {i === 0 && record!.scores.length > 1 ? ( 391 {record.scores.length > 1 && (
523 <button 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">
524 onClick={() => { 393 <img src={HistoryIcon} alt="History" className="w-4 h-4" />
525 (
526 document.querySelectorAll(
527 ".profileboard-record"
528 )[index % 20] as HTMLInputElement
529 ).style.height === "44px" ||
530 (
531 document.querySelectorAll(
532 ".profileboard-record"
533 )[index % 20] as HTMLInputElement
534 ).style.height === ""
535 ? ((
536 document.querySelectorAll(
537 ".profileboard-record"
538 )[index % 20] as HTMLInputElement
539 ).style.height =
540 `${record!.scores.length * 46}px`)
541 : ((
542 document.querySelectorAll(
543 ".profileboard-record"
544 )[index % 20] as HTMLInputElement
545 ).style.height = "44px");
546 }}
547 >
548 <img src={HistoryIcon} alt="history" />
549 </button> 394 </button>
550 ) : (
551 ""
552 )} 395 )}
553 </span> 396 </>
554 </> 397 )}
555 ))} 398 </div>
556 </button> 399 </div>
557 ); 400 );
558 } else { 401 })
559 return null; 402 )}
560 } 403 </div>
561 }) 404 </section>
562 ) : ( 405 )}
563 <>{console.warn(maps)}</>
564 )}
565 </div>
566 </section>
567 </main> 406 </main>
568 ); 407 );
569}; 408};