aboutsummaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2024-10-31 23:58:14 +0300
committerArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2024-10-31 23:58:14 +0300
commitf5ad360d02303ce27a65e67feb8ae38f1e26742c (patch)
treeb3e5b0fde361223275b8cc91fe0ccbdbb945ed13 /frontend
parentbackend: remove auth check for viewing user page (diff)
downloadlphub-f5ad360d02303ce27a65e67feb8ae38f1e26742c.tar.gz
lphub-f5ad360d02303ce27a65e67feb8ae38f1e26742c.tar.bz2
lphub-f5ad360d02303ce27a65e67feb8ae38f1e26742c.zip
frontend: convert all native confirm and alerts to custom hooks
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/Discussions.tsx12
-rw-r--r--frontend/src/components/Leaderboards.tsx4
-rw-r--r--frontend/src/components/ModMenu.tsx16
-rw-r--r--frontend/src/hooks/UseConfirm.tsx2
-rw-r--r--frontend/src/pages/Profile.tsx396
-rw-r--r--frontend/src/pages/User.tsx10
6 files changed, 226 insertions, 214 deletions
diff --git a/frontend/src/components/Discussions.tsx b/frontend/src/components/Discussions.tsx
index c22de79..0522910 100644
--- a/frontend/src/components/Discussions.tsx
+++ b/frontend/src/components/Discussions.tsx
@@ -6,6 +6,7 @@ import { time_ago } from '../utils/Time';
6import { API } from '../api/Api'; 6import { API } from '../api/Api';
7import "../css/Maps.css" 7import "../css/Maps.css"
8import { Link } from 'react-router-dom'; 8import { Link } from 'react-router-dom';
9import useConfirm from '../hooks/UseConfirm';
9 10
10interface DiscussionsProps { 11interface DiscussionsProps {
11 token?: string 12 token?: string
@@ -17,6 +18,8 @@ interface DiscussionsProps {
17 18
18const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, mapID, onRefresh }) => { 19const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, mapID, onRefresh }) => {
19 20
21 const { confirm, ConfirmDialogComponent } = useConfirm();
22
20 const [discussionThread, setDiscussionThread] = React.useState<MapDiscussion | undefined>(undefined); 23 const [discussionThread, setDiscussionThread] = React.useState<MapDiscussion | undefined>(undefined);
21 const [discussionSearch, setDiscussionSearch] = React.useState<string>(""); 24 const [discussionSearch, setDiscussionSearch] = React.useState<string>("");
22 25
@@ -48,7 +51,7 @@ const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, map
48 }; 51 };
49 52
50 const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => { 53 const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => {
51 if (window.confirm(`Are you sure you want to remove post: ${discussion.title}?`)) { 54 if (await confirm("Delete Map Discussion", `Are you sure you want to remove post: ${discussion.title}?`)) {
52 if (token) { 55 if (token) {
53 await API.delete_map_discussion(token, mapID, discussion.id); 56 await API.delete_map_discussion(token, mapID, discussion.id);
54 onRefresh(); 57 onRefresh();
@@ -58,6 +61,7 @@ const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, map
58 61
59 return ( 62 return (
60 <section id='section7' className='summary3'> 63 <section id='section7' className='summary3'>
64 {ConfirmDialogComponent}
61 <div id='discussion-search'> 65 <div id='discussion-search'>
62 <input type="text" value={discussionSearch} placeholder={"Search for posts..."} onChange={(e) => setDiscussionSearch(e.target.value)} /> 66 <input type="text" value={discussionSearch} placeholder={"Search for posts..."} onChange={(e) => setDiscussionSearch(e.target.value)} />
63 <div><button onClick={() => setCreateDiscussion(true)}>New Post</button></div> 67 <div><button onClick={() => setCreateDiscussion(true)}>New Post</button></div>
@@ -119,9 +123,9 @@ const Discussions: React.FC<DiscussionsProps> = ({ token, data, isModerator, map
119 } 123 }
120 </div> 124 </div>
121 <div id='discussion-send'> 125 <div id='discussion-send'>
122 <input type="text" value={createDiscussionCommentContent} placeholder={"Message"} 126 <input type="text" value={createDiscussionCommentContent} placeholder={"Message"}
123 onKeyDown={(e) => e.key === "Enter" && _create_map_discussion_comment(discussionThread.discussion.id)} 127 onKeyDown={(e) => e.key === "Enter" && _create_map_discussion_comment(discussionThread.discussion.id)}
124 onChange={(e) => setCreateDiscussionCommentContent(e.target.value)} /> 128 onChange={(e) => setCreateDiscussionCommentContent(e.target.value)} />
125 <div><button onClick={() => { 129 <div><button onClick={() => {
126 if (createDiscussionCommentContent !== "") { 130 if (createDiscussionCommentContent !== "") {
127 _create_map_discussion_comment(discussionThread.discussion.id); 131 _create_map_discussion_comment(discussionThread.discussion.id);
diff --git a/frontend/src/components/Leaderboards.tsx b/frontend/src/components/Leaderboards.tsx
index 4a107ad..aaaee62 100644
--- a/frontend/src/components/Leaderboards.tsx
+++ b/frontend/src/components/Leaderboards.tsx
@@ -91,14 +91,14 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ data }) => {
91 91
92 {r.kind === "multiplayer" ? ( 92 {r.kind === "multiplayer" ? (
93 <span> 93 <span>
94 <button onClick={() => { window.alert(`Host Demo ID: ${r.host_demo_id} \nParnter Demo ID: ${r.partner_demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 94 <button onClick={() => { message("Demo Information", `Host Demo ID: ${r.host_demo_id} \nParnter Demo ID: ${r.partner_demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
95 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.partner_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(160deg) contrast(60%) saturate(1000%)" }} /></button> 95 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.partner_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(160deg) contrast(60%) saturate(1000%)" }} /></button>
96 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.host_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(300deg) contrast(60%) saturate(1000%)" }} /></button> 96 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.host_demo_id}`}><img src={DownloadIcon} alt="download" style={{ filter: "hue-rotate(300deg) contrast(60%) saturate(1000%)" }} /></button>
97 </span> 97 </span>
98 ) : r.kind === "singleplayer" && ( 98 ) : r.kind === "singleplayer" && (
99 99
100 <span> 100 <span>
101 <button onClick={() => { message("Demo information", `Demo ID: ${r.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 101 <button onClick={() => { message("Demo Information", `Demo ID: ${r.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
102 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 102 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${r.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
103 </span> 103 </span>
104 )} 104 )}
diff --git a/frontend/src/components/ModMenu.tsx b/frontend/src/components/ModMenu.tsx
index 5b0d1c8..2fb1737 100644
--- a/frontend/src/components/ModMenu.tsx
+++ b/frontend/src/components/ModMenu.tsx
@@ -1,11 +1,12 @@
1import React from 'react'; 1import React from 'react';
2import ReactMarkdown from 'react-markdown'; 2import ReactMarkdown from 'react-markdown';
3import { useNavigate } from 'react-router-dom';
3 4
4import { MapSummary } from '../types/Map'; 5import { MapSummary } from '../types/Map';
5import { ModMenuContent } from '../types/Content'; 6import { ModMenuContent } from '../types/Content';
6import { API } from '../api/Api'; 7import { API } from '../api/Api';
7import "../css/ModMenu.css" 8import "../css/ModMenu.css"
8import { useNavigate } from 'react-router-dom'; 9import useConfirm from '../hooks/UseConfirm';
9 10
10interface ModMenuProps { 11interface ModMenuProps {
11 token?: string; 12 token?: string;
@@ -16,6 +17,8 @@ interface ModMenuProps {
16 17
17const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) => { 18const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) => {
18 19
20 const { confirm, ConfirmDialogComponent } = useConfirm();
21
19 const [menu, setMenu] = React.useState<number>(0); 22 const [menu, setMenu] = React.useState<number>(0);
20 const [showButton, setShowButton] = React.useState<boolean>(true); 23 const [showButton, setShowButton] = React.useState<boolean>(true);
21 24
@@ -64,7 +67,7 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
64 }; 67 };
65 68
66 const _edit_map_summary_image = async () => { 69 const _edit_map_summary_image = async () => {
67 if (window.confirm("Are you sure you want to submit this to the database?")) { 70 if (await confirm("Edit Map Summary Image", "Are you sure you want to submit this to the database?")) {
68 if (token) { 71 if (token) {
69 const success = await API.put_map_image(token, mapID, image); 72 const success = await API.put_map_image(token, mapID, image);
70 if (success) { 73 if (success) {
@@ -77,7 +80,7 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
77 }; 80 };
78 81
79 const _edit_map_summary_route = async () => { 82 const _edit_map_summary_route = async () => {
80 if (window.confirm("Are you sure you want to submit this to the database?")) { 83 if (await confirm("Edit Map Summary Route", "Are you sure you want to submit this to the database?")) {
81 if (token) { 84 if (token) {
82 routeContent.date += "T00:00:00Z"; 85 routeContent.date += "T00:00:00Z";
83 const success = await API.put_map_summary(token, mapID, routeContent); 86 const success = await API.put_map_summary(token, mapID, routeContent);
@@ -91,7 +94,7 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
91 }; 94 };
92 95
93 const _create_map_summary_route = async () => { 96 const _create_map_summary_route = async () => {
94 if (window.confirm("Are you sure you want to submit this to the database?")) { 97 if (await confirm("Create Map Summary Route", "Are you sure you want to submit this to the database?")) {
95 if (token) { 98 if (token) {
96 routeContent.date += "T00:00:00Z"; 99 routeContent.date += "T00:00:00Z";
97 const success = await API.post_map_summary(token, mapID, routeContent); 100 const success = await API.post_map_summary(token, mapID, routeContent);
@@ -105,8 +108,8 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
105 }; 108 };
106 109
107 const _delete_map_summary_route = async () => { 110 const _delete_map_summary_route = async () => {
108 if (window.confirm(`Are you sure you want to delete this run from the database? 111 if (await confirm("Delete Map Summary Route", `Are you sure you want to submit this to the database?\n
109 ${data.summary.routes[selectedRun].category.name} ${data.summary.routes[selectedRun].history.score_count} portals ${data.summary.routes[selectedRun].history.runner_name}`)) { 112 ${data.summary.routes[selectedRun].category.name}\n${data.summary.routes[selectedRun].history.score_count} portals\n${data.summary.routes[selectedRun].history.runner_name}`)) {
110 if (token) { 113 if (token) {
111 const success = await API.delete_map_summary(token, mapID, data.summary.routes[selectedRun].route_id); 114 const success = await API.delete_map_summary(token, mapID, data.summary.routes[selectedRun].route_id);
112 if (success) { 115 if (success) {
@@ -160,6 +163,7 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
160 163
161 return ( 164 return (
162 <> 165 <>
166 {ConfirmDialogComponent}
163 <div id="modview_block" /> 167 <div id="modview_block" />
164 <div id='modview'> 168 <div id='modview'>
165 <div> 169 <div>
diff --git a/frontend/src/hooks/UseConfirm.tsx b/frontend/src/hooks/UseConfirm.tsx
index 9a7853b..0d64224 100644
--- a/frontend/src/hooks/UseConfirm.tsx
+++ b/frontend/src/hooks/UseConfirm.tsx
@@ -11,7 +11,7 @@ const useConfirm = () => {
11 setIsOpen(true); 11 setIsOpen(true);
12 setTitle(titleN); 12 setTitle(titleN);
13 setSubtitle(subtitleN); 13 setSubtitle(subtitleN);
14 return new Promise((resolve) => { 14 return new Promise<boolean>((resolve) => {
15 setResolvePromise(() => resolve); 15 setResolvePromise(() => resolve);
16 }); 16 });
17 }; 17 };
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 5c5676e..590bb9b 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -71,10 +71,10 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
71 return; 71 return;
72 } 72 }
73 73
74 messageLoad("Deleting..."); 74 messageLoad("Deleting...");
75 75
76 const api_success = await API.delete_map_record(token!, map_id, record_id); 76 const api_success = await API.delete_map_record(token!, map_id, record_id);
77 messageLoadClose(); 77 messageLoadClose();
78 if (api_success) { 78 if (api_success) {
79 await message("Delete Record", "Successfully deleted record."); 79 await message("Delete Record", "Successfully deleted record.");
80 onDeleteRecord(); 80 onDeleteRecord();
@@ -108,240 +108,240 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
108 }; 108 };
109 109
110 return ( 110 return (
111 <div> 111 <div>
112 {MessageDialogComponent} 112 {MessageDialogComponent}
113 {MessageDialogLoadComponent} 113 {MessageDialogLoadComponent}
114 {ConfirmDialogComponent} 114 {ConfirmDialogComponent}
115 115
116 <main> 116 <main>
117 <section id='section1' className='profile'> 117 <section id='section1' className='profile'>
118 118
119 {profile.profile 119 {profile.profile
120 ? ( 120 ? (
121 <div id='profile-image' onClick={_update_profile}> 121 <div id='profile-image' onClick={_update_profile}>
122 <img src={profile.avatar_link} alt="profile-image"></img> 122 <img src={profile.avatar_link} alt="profile-image"></img>
123 <span>Refresh</span> 123 <span>Refresh</span>
124 </div>
125 ) : (
126 <div>
127 <img src={profile.avatar_link} alt="profile-image"></img>
128 </div>
129 )}
130
131 <div id='profile-top'>
132 <div>
133 <div>{profile.user_name}</div>
134 <div>
135 {profile.country_code === "XX" ? "" : <img src={`https://flagcdn.com/w80/${profile.country_code.toLowerCase()}.jpg`} alt={profile.country_code} />}
136 </div>
137 <div>
138 {profile.titles.map(e => (
139 <span className="titles" style={{ backgroundColor: `#${e.color}` }}>
140 {e.name}
141 </span>
142 ))}
143 </div>
124 </div> 144 </div>
125 ) : (
126 <div> 145 <div>
127 <img src={profile.avatar_link} alt="profile-image"></img> 146 {profile.links.steam === "-" ? "" : <a href={profile.links.steam}><img src={SteamIcon} alt="Steam" /></a>}
147 {profile.links.twitch === "-" ? "" : <a href={profile.links.twitch}><img src={TwitchIcon} alt="Twitch" /></a>}
148 {profile.links.youtube === "-" ? "" : <a href={profile.links.youtube}><img src={YouTubeIcon} alt="Youtube" /></a>}
149 {profile.links.p2sr === "-" ? "" : <a href={profile.links.p2sr}><img src={PortalIcon} alt="P2SR" style={{ padding: "0" }} /></a>}
128 </div> 150 </div>
129 )}
130 151
131 <div id='profile-top'> 152 </div>
132 <div> 153 <div id='profile-bottom'>
133 <div>{profile.user_name}</div>
134 <div> 154 <div>
135 {profile.country_code === "XX" ? "" : <img src={`https://flagcdn.com/w80/${profile.country_code.toLowerCase()}.jpg`} alt={profile.country_code} />} 155 <span>Overall</span>
156 <span>{profile.rankings.overall.rank === 0 ? "N/A " : "#" + profile.rankings.overall.rank + " "}
157 <span>({profile.rankings.overall.completion_count}/{profile.rankings.overall.completion_total})</span>
158 </span>
136 </div> 159 </div>
137 <div> 160 <div>
138 {profile.titles.map(e => ( 161 <span>Singleplayer</span>
139 <span className="titles" style={{ backgroundColor: `#${e.color}` }}> 162 <span>{profile.rankings.singleplayer.rank === 0 ? "N/A " : "#" + profile.rankings.singleplayer.rank + " "}
140 {e.name} 163 <span>({profile.rankings.singleplayer.completion_count}/{profile.rankings.singleplayer.completion_total})</span>
141 </span> 164 </span>
142 ))} 165 </div>
166 <div>
167 <span>Cooperative</span>
168 <span>{profile.rankings.cooperative.rank === 0 ? "N/A " : "#" + profile.rankings.cooperative.rank + " "}
169 <span>({profile.rankings.cooperative.completion_count}/{profile.rankings.cooperative.completion_total})</span>
170 </span>
143 </div> 171 </div>
144 </div> 172 </div>
145 <div> 173 </section>
146 {profile.links.steam === "-" ? "" : <a href={profile.links.steam}><img src={SteamIcon} alt="Steam" /></a>}
147 {profile.links.twitch === "-" ? "" : <a href={profile.links.twitch}><img src={TwitchIcon} alt="Twitch" /></a>}
148 {profile.links.youtube === "-" ? "" : <a href={profile.links.youtube}><img src={YouTubeIcon} alt="Youtube" /></a>}
149 {profile.links.p2sr === "-" ? "" : <a href={profile.links.p2sr}><img src={PortalIcon} alt="P2SR" style={{ padding: "0" }} /></a>}
150 </div>
151
152 </div>
153 <div id='profile-bottom'>
154 <div>
155 <span>Overall</span>
156 <span>{profile.rankings.overall.rank === 0 ? "N/A " : "#" + profile.rankings.overall.rank + " "}
157 <span>({profile.rankings.overall.completion_count}/{profile.rankings.overall.completion_total})</span>
158 </span>
159 </div>
160 <div>
161 <span>Singleplayer</span>
162 <span>{profile.rankings.singleplayer.rank === 0 ? "N/A " : "#" + profile.rankings.singleplayer.rank + " "}
163 <span>({profile.rankings.singleplayer.completion_count}/{profile.rankings.singleplayer.completion_total})</span>
164 </span>
165 </div>
166 <div>
167 <span>Cooperative</span>
168 <span>{profile.rankings.cooperative.rank === 0 ? "N/A " : "#" + profile.rankings.cooperative.rank + " "}
169 <span>({profile.rankings.cooperative.completion_count}/{profile.rankings.cooperative.completion_total})</span>
170 </span>
171 </div>
172 </div>
173 </section>
174
175
176 <section id='section2' className='profile'>
177 <button onClick={() => setNavState(0)}><img src={FlagIcon} alt="" />&nbsp;Player Records</button>
178 <button onClick={() => setNavState(1)}><img src={StatisticsIcon} alt="" />&nbsp;Statistics</button>
179 </section>
180 174
181 175
176 <section id='section2' className='profile'>
177 <button onClick={() => setNavState(0)}><img src={FlagIcon} alt="" />&nbsp;Player Records</button>
178 <button onClick={() => setNavState(1)}><img src={StatisticsIcon} alt="" />&nbsp;Statistics</button>
179 </section>
182 180
183 181
184 182
185 <section id='section3' className='profile1'>
186 <div id='profileboard-nav'>
187 {gameData === null ? <select>error</select> :
188 183
189 <select id='select-game'
190 onChange={() => {
191 setGame((document.querySelector('#select-game') as HTMLInputElement).value);
192 setChapter("0");
193 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement;
194 if (chapterSelect) {
195 chapterSelect.value = "0";
196 }
197 }}>
198 <option value={0} key={0}>All Scores</option>
199 {gameData.map((e, i) => (
200 <option value={e.id} key={i + 1}>{e.name}</option>
201 ))}</select>
202 }
203 184
204 {game === "0" ? 185 <section id='section3' className='profile1'>
205 <select disabled> 186 <div id='profileboard-nav'>
206 <option>All Chapters</option> 187 {gameData === null ? <select>error</select> :
207 </select>
208 : chapterData === null ? <select></select> :
209 188
210 <select id='select-chapter' 189 <select id='select-game'
211 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}> 190 onChange={() => {
212 <option value="0" key="0">All Chapters</option> 191 setGame((document.querySelector('#select-game') as HTMLInputElement).value);
213 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => ( 192 setChapter("0");
193 const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement;
194 if (chapterSelect) {
195 chapterSelect.value = "0";
196 }
197 }}>
198 <option value={0} key={0}>All Scores</option>
199 {gameData.map((e, i) => (
214 <option value={e.id} key={i + 1}>{e.name}</option> 200 <option value={e.id} key={i + 1}>{e.name}</option>
215 ))}</select> 201 ))}</select>
216 } 202 }
217 </div> 203
218 <div id='profileboard-top'> 204 {game === "0" ?
219 <span><span>Map Name</span><img src={SortIcon} alt="" /></span> 205 <select disabled>
220 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span> 206 <option>All Chapters</option>
221 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span> 207 </select>
222 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span> 208 : chapterData === null ? <select></select> :
223 <span> </span> 209
224 <span><span>Rank</span><img src={SortIcon} alt="" /></span> 210 <select id='select-chapter'
225 <span><span>Date</span><img src={SortIcon} alt="" /></span> 211 onChange={() => setChapter((document.querySelector('#select-chapter') as HTMLInputElement).value)}>
226 <div id='page-number'> 212 <option value="0" key="0">All Chapters</option>
227 <div> 213 {chapterData.chapters.filter(e => e.is_disabled === false).map((e, i) => (
228 <button onClick={() => { 214 <option value={e.id} key={i + 1}>{e.name}</option>
229 if (pageNumber !== 1) { 215 ))}</select>
230 setPageNumber(prevPageNumber => prevPageNumber - 1); 216 }
231 const records = document.querySelectorAll(".profileboard-record"); 217 </div>
232 records.forEach((r) => { 218 <div id='profileboard-top'>
233 (r as HTMLInputElement).style.height = "44px"; 219 <span><span>Map Name</span><img src={SortIcon} alt="" /></span>
234 }); 220 <span style={{ justifyContent: 'center' }}><span>Portals</span><img src={SortIcon} alt="" /></span>
235 } 221 <span style={{ justifyContent: 'center' }}><span>WRΔ </span><img src={SortIcon} alt="" /></span>
236 }} 222 <span style={{ justifyContent: 'center' }}><span>Time</span><img src={SortIcon} alt="" /></span>
237 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button> 223 <span> </span>
238 <span>{pageNumber}/{pageMax}</span> 224 <span><span>Rank</span><img src={SortIcon} alt="" /></span>
239 <button onClick={() => { 225 <span><span>Date</span><img src={SortIcon} alt="" /></span>
240 if (pageNumber !== pageMax) { 226 <div id='page-number'>
241 setPageNumber(prevPageNumber => prevPageNumber + 1); 227 <div>
242 const records = document.querySelectorAll(".profileboard-record"); 228 <button onClick={() => {
243 records.forEach((r) => { 229 if (pageNumber !== 1) {
244 (r as HTMLInputElement).style.height = "44px"; 230 setPageNumber(prevPageNumber => prevPageNumber - 1);
245 }); 231 const records = document.querySelectorAll(".profileboard-record");
246 } 232 records.forEach((r) => {
247 }} 233 (r as HTMLInputElement).style.height = "44px";
248 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button> 234 });
235 }
236 }}
237 ><i className='triangle' style={{ position: 'relative', left: '-5px', }}></i> </button>
238 <span>{pageNumber}/{pageMax}</span>
239 <button onClick={() => {
240 if (pageNumber !== pageMax) {
241 setPageNumber(prevPageNumber => prevPageNumber + 1);
242 const records = document.querySelectorAll(".profileboard-record");
243 records.forEach((r) => {
244 (r as HTMLInputElement).style.height = "44px";
245 });
246 }
247 }}
248 ><i className='triangle' style={{ position: 'relative', left: '5px', transform: 'rotate(180deg)' }}></i> </button>
249 </div>
249 </div> 250 </div>
250 </div> 251 </div>
251 </div> 252 <hr />
252 <hr /> 253 <div id='profileboard-records'>
253 <div id='profileboard-records'>
254 254
255 {game === "0" 255 {game === "0"
256 ? ( 256 ? (
257 257
258 profile.records.sort((a, b) => a.map_id - b.map_id) 258 profile.records.sort((a, b) => a.map_id - b.map_id)
259 .map((r, index) => ( 259 .map((r, index) => (
260 260
261 Math.ceil((index + 1) / 20) === pageNumber ? ( 261 Math.ceil((index + 1) / 20) === pageNumber ? (
262 <button className="profileboard-record" key={index}>
263 {r.scores.map((e, i) => (<>
264 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
265
266 <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link>
267
268 <span style={{ display: "grid" }}>{e.score_count}</span>
269
270 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : e.score_count - r.map_wr_count}</span>
271 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span>
272 <span> </span>
273 {i === 0 ? <span>#{r.placement}</span> : <span> </span>}
274 <span>{e.date.split("T")[0]}</span>
275 <span style={{ flexDirection: "row-reverse" }}>
276
277 <button style={{ marginRight: "10px" }} onClick={() => { message("Demo information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
278 <button onClick={() => { _delete_submission(r.map_id, e.record_id) }}><img src={DeleteIcon}></img></button>
279 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
280 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
281 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
282 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
283 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${r.scores.length * 46}px` :
284 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
285 }
286 }><img src={HistoryIcon} alt="history" /></button> : ""}
287
288 </span>
289 </>))}
290
291 </button>
292 ) : ""
293 ))) : maps ?
294
295 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id)
296 .map((r, index) => {
297 if (Math.ceil((index + 1) / 20) === pageNumber) {
298 let record = profile.records.find((e) => e.map_id === r.id);
299 return record === undefined ? (
300 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}>
301 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
302 <span style={{ display: "grid" }}>N/A</span>
303 <span style={{ display: "grid" }}>N/A</span>
304 <span>N/A</span>
305 <span> </span>
306 <span>N/A</span>
307 <span>N/A</span>
308 <span style={{ flexDirection: "row-reverse" }}></span>
309 </button>
310 ) : (
311 <button className="profileboard-record" key={index}> 262 <button className="profileboard-record" key={index}>
312 {record.scores.map((e, i) => (<> 263 {r.scores.map((e, i) => (<>
313 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} 264 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
314 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link> 265
315 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span> 266 <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link>
316 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count > 0 ? `+${record!.scores[i].score_count - record!.map_wr_count}` : record!.scores[i].score_count - record!.map_wr_count}</span> 267
317 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span> 268 <span style={{ display: "grid" }}>{e.score_count}</span>
269
270 <span style={{ display: "grid" }}>{e.score_count - r.map_wr_count > 0 ? `+${e.score_count - r.map_wr_count}` : e.score_count - r.map_wr_count}</span>
271 <span style={{ display: "grid" }}>{ticks_to_time(e.score_time)}</span>
318 <span> </span> 272 <span> </span>
319 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>} 273 {i === 0 ? <span>#{r.placement}</span> : <span> </span>}
320 <span>{record!.scores[i].date.split("T")[0]}</span> 274 <span>{e.date.split("T")[0]}</span>
321 <span style={{ flexDirection: "row-reverse" }}> 275 <span style={{ flexDirection: "row-reverse" }}>
322 276
323 <button onClick={() => { message("Demo information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 277 <button style={{ marginRight: "10px" }} onClick={() => { message("Demo Information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
324 <button onClick={() => { _delete_submission(r.id, e.record_id) }}><img src={DeleteIcon}></img></button> 278 <button onClick={() => { _delete_submission(r.map_id, e.record_id) }}><img src={DeleteIcon}></img></button>
325 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 279 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
326 {i === 0 && record!.scores.length > 1 ? <button onClick={() => { 280 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
327 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 281 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
328 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? 282 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
329 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${record!.scores.length * 46}px` : 283 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${r.scores.length * 46}px` :
330 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px" 284 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
331 } 285 }
332 }><img src={HistoryIcon} alt="history" /></button> : ""} 286 }><img src={HistoryIcon} alt="history" /></button> : ""}
333 287
334 </span> 288 </span>
335 </>))} 289 </>))}
336 </button>
337 290
338 ) 291 </button>
339 } else { return null } 292 ) : ""
340 }) : (<>{console.warn(maps)}</>)} 293 ))) : maps ?
341 </div> 294
342 </section> 295 maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id)
343 </main> 296 .map((r, index) => {
344 </div> 297 if (Math.ceil((index + 1) / 20) === pageNumber) {
298 let record = profile.records.find((e) => e.map_id === r.id);
299 return record === undefined ? (
300 <button className="profileboard-record" key={index} style={{ backgroundColor: "#1b1b20" }}>
301 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
302 <span style={{ display: "grid" }}>N/A</span>
303 <span style={{ display: "grid" }}>N/A</span>
304 <span>N/A</span>
305 <span> </span>
306 <span>N/A</span>
307 <span>N/A</span>
308 <span style={{ flexDirection: "row-reverse" }}></span>
309 </button>
310 ) : (
311 <button className="profileboard-record" key={index}>
312 {record.scores.map((e, i) => (<>
313 {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""}
314 <Link to={`/maps/${r.id}`}><span>{r.name}</span></Link>
315 <span style={{ display: "grid" }}>{record!.scores[i].score_count}</span>
316 <span style={{ display: "grid" }}>{record!.scores[i].score_count - record!.map_wr_count > 0 ? `+${record!.scores[i].score_count - record!.map_wr_count}` : record!.scores[i].score_count - record!.map_wr_count}</span>
317 <span style={{ display: "grid" }}>{ticks_to_time(record!.scores[i].score_time)}</span>
318 <span> </span>
319 {i === 0 ? <span>#{record!.placement}</span> : <span> </span>}
320 <span>{record!.scores[i].date.split("T")[0]}</span>
321 <span style={{ flexDirection: "row-reverse" }}>
322
323 <button onClick={() => { message("Demo Information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
324 <button onClick={() => { _delete_submission(r.id, e.record_id) }}><img src={DeleteIcon}></img></button>
325 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
326 {i === 0 && record!.scores.length > 1 ? <button onClick={() => {
327 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
328 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ?
329 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = `${record!.scores.length * 46}px` :
330 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height = "44px"
331 }
332 }><img src={HistoryIcon} alt="history" /></button> : ""}
333
334 </span>
335 </>))}
336 </button>
337
338 )
339 } else { return null }
340 }) : (<>{console.warn(maps)}</>)}
341 </div>
342 </section>
343 </main>
344 </div>
345 ); 345 );
346}; 346};
347 347
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx
index c1175bb..ad230bd 100644
--- a/frontend/src/pages/User.tsx
+++ b/frontend/src/pages/User.tsx
@@ -8,6 +8,7 @@ import { Map } from '../types/Map';
8import { API } from '../api/Api'; 8import { API } from '../api/Api';
9import { ticks_to_time } from '../utils/Time'; 9import { ticks_to_time } from '../utils/Time';
10import "../css/Profile.css"; 10import "../css/Profile.css";
11import useMessage from '../hooks/UseMessage';
11 12
12interface UserProps { 13interface UserProps {
13 profile?: UserProfile; 14 profile?: UserProfile;
@@ -17,6 +18,8 @@ interface UserProps {
17 18
18const User: React.FC<UserProps> = ({ token, profile, gameData }) => { 19const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
19 20
21 const { message, MessageDialogComponent } = useMessage();
22
20 const [user, setUser] = React.useState<UserProfile | undefined>(undefined); 23 const [user, setUser] = React.useState<UserProfile | undefined>(undefined);
21 24
22 const [navState, setNavState] = React.useState(0); 25 const [navState, setNavState] = React.useState(0);
@@ -89,6 +92,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
89 92
90 return ( 93 return (
91 <main> 94 <main>
95 {MessageDialogComponent}
92 <section id='section1' className='profile'> 96 <section id='section1' className='profile'>
93 <div> 97 <div>
94 <img src={user.avatar_link} alt="profile-image"></img> 98 <img src={user.avatar_link} alt="profile-image"></img>
@@ -190,7 +194,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
190 <span><span>Date</span><img src={SortIcon} alt="" /></span> 194 <span><span>Date</span><img src={SortIcon} alt="" /></span>
191 <div id='page-number'> 195 <div id='page-number'>
192 <div> 196 <div>
193 <button onClick={() => { 197 <button onClick={() => {
194 if (pageNumber !== 1) { 198 if (pageNumber !== 1) {
195 setPageNumber(prevPageNumber => prevPageNumber - 1); 199 setPageNumber(prevPageNumber => prevPageNumber - 1);
196 const records = document.querySelectorAll(".profileboard-record"); 200 const records = document.querySelectorAll(".profileboard-record");
@@ -239,7 +243,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
239 <span>{e.date.split("T")[0]}</span> 243 <span>{e.date.split("T")[0]}</span>
240 <span style={{ flexDirection: "row-reverse" }}> 244 <span style={{ flexDirection: "row-reverse" }}>
241 245
242 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 246 <button onClick={() => { message("Demo Information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
243 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 247 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
244 {i === 0 && r.scores.length > 1 ? <button onClick={() => { 248 {i === 0 && r.scores.length > 1 ? <button onClick={() => {
245 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 249 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||
@@ -284,7 +288,7 @@ const User: React.FC<UserProps> = ({ token, profile, gameData }) => {
284 <span>{record!.scores[i].date.split("T")[0]}</span> 288 <span>{record!.scores[i].date.split("T")[0]}</span>
285 <span style={{ flexDirection: "row-reverse" }}> 289 <span style={{ flexDirection: "row-reverse" }}>
286 290
287 <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> 291 <button onClick={() => { message("Demo Information", `Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button>
288 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> 292 <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button>
289 {i === 0 && record!.scores.length > 1 ? <button onClick={() => { 293 {i === 0 && record!.scores.length > 1 ? <button onClick={() => {
290 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || 294 (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" ||