aboutsummaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/App.tsx28
-rw-r--r--frontend/src/api/Maps.tsx6
-rw-r--r--frontend/src/components/Leaderboards.tsx6
-rw-r--r--frontend/src/components/MessageDialog.tsx2
-rw-r--r--frontend/src/components/UploadRunDialog.tsx32
-rw-r--r--frontend/src/css/Dialog.css3
-rw-r--r--frontend/src/css/UploadRunDialog.css7
-rw-r--r--frontend/src/hooks/UseConfirm.tsx2
-rw-r--r--frontend/src/hooks/UseMessage.tsx6
-rw-r--r--frontend/src/pages/Profile.tsx7
10 files changed, 47 insertions, 52 deletions
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d1d501d..c6952b1 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,5 +1,5 @@
1import React from 'react'; 1import React from 'react';
2import { Routes, Route, useLocation } from "react-router-dom"; 2import { Routes, Route } from "react-router-dom";
3 3
4import { UserProfile } from './types/Profile'; 4import { UserProfile } from './types/Profile';
5import Sidebar from './components/Sidebar'; 5import Sidebar from './components/Sidebar';
@@ -18,22 +18,15 @@ import { API } from './api/Api';
18import Maplist from './pages/Maplist'; 18import Maplist from './pages/Maplist';
19import Rankings from './pages/Rankings'; 19import Rankings from './pages/Rankings';
20import { get_user_id_from_token, get_user_mod_from_token } from './utils/Jwt'; 20import { get_user_id_from_token, get_user_mod_from_token } from './utils/Jwt';
21import { MapDeleteEndpoint } from './types/Map';
22 21
23const App: React.FC = () => { 22const App: React.FC = () => {
24 const [token, setToken] = React.useState<string | undefined>(undefined); 23 const [token, setToken] = React.useState<string | undefined>(undefined);
25 const [profile, setProfile] = React.useState<UserProfile | undefined>(undefined); 24 const [profile, setProfile] = React.useState<UserProfile | undefined>(undefined);
26 const [isModerator, setIsModerator] = React.useState<boolean>(false); 25 const [isModerator, setIsModerator] = React.useState<boolean>(false);
27 26
28 const [msgIsOpen, setMsgIsOpen] = React.useState<boolean>(false);
29
30 const [games, setGames] = React.useState<Game[]>([]); 27 const [games, setGames] = React.useState<Game[]>([]);
31 28
32 const [uploadRunDialog, setUploadRunDialog] = React.useState<boolean>(false); 29 const [uploadRunDialog, setUploadRunDialog] = React.useState<boolean>(false);
33 const [uploadRunDialogMapID, setUploadRunDialogMapID] = React.useState<number | undefined>(undefined);
34
35 const [confirmDialogOpen, setConfirmDialogOpen] = React.useState<boolean>(false);
36 const [currDeleteMapInfo, setCurrDeleteMapInfo] = React.useState<MapDeleteEndpoint>();
37 30
38 const _fetch_token = async () => { 31 const _fetch_token = async () => {
39 const token = await API.get_token(); 32 const token = await API.get_token();
@@ -45,10 +38,9 @@ const App: React.FC = () => {
45 setGames(games); 38 setGames(games);
46 }; 39 };
47 40
48 const _set_profile = async (user_id: string | undefined) => { 41 const _set_profile = async (user_id?: string) => {
49 if (user_id) { 42 if (user_id && token) {
50 setProfile({} as UserProfile); // placeholder before we call actual user profile 43 const user = await API.get_profile(token);
51 const user = await API.get_profile(token!);
52 setProfile(user); 44 setProfile(user);
53 } 45 }
54 }; 46 };
@@ -58,6 +50,7 @@ const App: React.FC = () => {
58 setProfile(undefined); 50 setProfile(undefined);
59 setIsModerator(false); 51 setIsModerator(false);
60 } else { 52 } else {
53 setProfile({} as UserProfile); // placeholder before we call actual user profile
61 _set_profile(get_user_id_from_token(token)) 54 _set_profile(get_user_id_from_token(token))
62 const modStatus = get_user_mod_from_token(token) 55 const modStatus = get_user_mod_from_token(token)
63 if (modStatus) { 56 if (modStatus) {
@@ -81,15 +74,20 @@ const App: React.FC = () => {
81 74
82 return ( 75 return (
83 <> 76 <>
84 <UploadRunDialog token={token} open={uploadRunDialog} onClose={() => setUploadRunDialog(false)} games={games} /> 77 <UploadRunDialog token={token} open={uploadRunDialog} onClose={(updateProfile) => {
78 setUploadRunDialog(false);
79 if (updateProfile) {
80 _set_profile(get_user_id_from_token(token));
81 }
82 }} games={games} />
85 <Sidebar setToken={setToken} profile={profile} setProfile={setProfile} onUploadRun={() => setUploadRunDialog(true)} /> 83 <Sidebar setToken={setToken} profile={profile} setProfile={setProfile} onUploadRun={() => setUploadRunDialog(true)} />
86 <Routes> 84 <Routes>
87 <Route path="/" element={<Homepage />} /> 85 <Route path="/" element={<Homepage />} />
88 <Route path="/profile" element={<Profile profile={profile} token={token} gameData={games} onDeleteRecord={() => setConfirmDialogOpen(true)} />} /> 86 <Route path="/profile" element={<Profile profile={profile} token={token} gameData={games} onDeleteRecord={() => _set_profile(get_user_id_from_token(token))} />} />
89 <Route path="/users/*" element={<User profile={profile} token={token} gameData={games} />} /> 87 <Route path="/users/*" element={<User profile={profile} token={token} gameData={games} />} />
90 <Route path="/games" element={<Games games={games} />} /> 88 <Route path="/games" element={<Games games={games} />} />
91 <Route path='/games/:id' element={<Maplist />}></Route> 89 <Route path='/games/:id' element={<Maplist />}></Route>
92 <Route path="/maps/*" element={<Maps token={token} isModerator={isModerator} />}/> 90 <Route path="/maps/*" element={<Maps token={token} isModerator={isModerator} />} />
93 <Route path="/rules" element={<Rules />} /> 91 <Route path="/rules" element={<Rules />} />
94 <Route path="/about" element={<About />} /> 92 <Route path="/about" element={<About />} />
95 <Route path='/rankings' element={<Rankings />}></Route> 93 <Route path='/rankings' element={<Rankings />}></Route>
diff --git a/frontend/src/api/Maps.tsx b/frontend/src/api/Maps.tsx
index 2209788..80f88d4 100644
--- a/frontend/src/api/Maps.tsx
+++ b/frontend/src/api/Maps.tsx
@@ -75,7 +75,7 @@ export const delete_map_discussion = async (token: string, map_id: string, discu
75 return response.data.success; 75 return response.data.success;
76}; 76};
77 77
78export const post_record = async (token: string, run: UploadRunContent): Promise<string> => { 78export const post_record = async (token: string, run: UploadRunContent): Promise<[boolean, string]> => {
79 if (run.partner_demo) { 79 if (run.partner_demo) {
80 const response = await axios.postForm(url(`maps/${run.map_id}/record`), { 80 const response = await axios.postForm(url(`maps/${run.map_id}/record`), {
81 "host_demo": run.host_demo, 81 "host_demo": run.host_demo,
@@ -85,7 +85,7 @@ export const post_record = async (token: string, run: UploadRunContent): Promise
85 "Authorization": token, 85 "Authorization": token,
86 } 86 }
87 }); 87 });
88 return response.data.message; 88 return [ response.data.success, response.data.message ];
89 } else { 89 } else {
90 const response = await axios.postForm(url(`maps/${run.map_id}/record`), { 90 const response = await axios.postForm(url(`maps/${run.map_id}/record`), {
91 "host_demo": run.host_demo, 91 "host_demo": run.host_demo,
@@ -94,7 +94,7 @@ export const post_record = async (token: string, run: UploadRunContent): Promise
94 "Authorization": token, 94 "Authorization": token,
95 } 95 }
96 }); 96 });
97 return response.data.message; 97 return [ response.data.success, response.data.message ];
98 } 98 }
99} 99}
100 100
diff --git a/frontend/src/components/Leaderboards.tsx b/frontend/src/components/Leaderboards.tsx
index f4d666d..f86aa7b 100644
--- a/frontend/src/components/Leaderboards.tsx
+++ b/frontend/src/components/Leaderboards.tsx
@@ -40,8 +40,8 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ data }) => {
40 40
41 {data.map.is_coop ? ( 41 {data.map.is_coop ? (
42 <div id='runner'> 42 <div id='runner'>
43 <span>Host</span> 43 <span>Blue</span>
44 <span>Partner</span> 44 <span>Orange</span>
45 </div> 45 </div>
46 ) : ( 46 ) : (
47 <span>Runner</span> 47 <span>Runner</span>
@@ -87,7 +87,7 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ data }) => {
87 87
88 {r.kind === "multiplayer" ? ( 88 {r.kind === "multiplayer" ? (
89 <span> 89 <span>
90 <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> 90 <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>
91 <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> 91 <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>
92 <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> 92 <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>
93 </span> 93 </span>
diff --git a/frontend/src/components/MessageDialog.tsx b/frontend/src/components/MessageDialog.tsx
index 0a8db42..17b1258 100644
--- a/frontend/src/components/MessageDialog.tsx
+++ b/frontend/src/components/MessageDialog.tsx
@@ -19,7 +19,7 @@ const MessageDialog: React.FC<MessageDialogProps> = ({ title, subtitle, onClose
19 <span>{subtitle}</span> 19 <span>{subtitle}</span>
20 </div> 20 </div>
21 <div className='dialog-element dialog-btns-container'> 21 <div className='dialog-element dialog-btns-container'>
22 <button onClick={onClose}>Ok</button> 22 <button onClick={onClose}>Close</button>
23 </div> 23 </div>
24 </div> 24 </div>
25 </div> 25 </div>
diff --git a/frontend/src/components/UploadRunDialog.tsx b/frontend/src/components/UploadRunDialog.tsx
index 8081643..a0d23e7 100644
--- a/frontend/src/components/UploadRunDialog.tsx
+++ b/frontend/src/components/UploadRunDialog.tsx
@@ -13,18 +13,15 @@ import useConfirm from '../hooks/UseConfirm';
13interface UploadRunDialogProps { 13interface UploadRunDialogProps {
14 token?: string; 14 token?: string;
15 open: boolean; 15 open: boolean;
16 onClose: () => void; 16 onClose: (updateProfile: boolean) => void;
17 games: Game[]; 17 games: Game[];
18} 18}
19 19
20const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose, games }) => { 20const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose, games }) => {
21 21
22 const [confirmMessage, setConfirmMessage] = React.useState<string>("Are you sure you want to upload this demo?");
23
24 const { message, MessageDialogComponent } = useMessage(); 22 const { message, MessageDialogComponent } = useMessage();
25 const { confirm, ConfirmDialogComponent } = useConfirm(); 23 const { confirm, ConfirmDialogComponent } = useConfirm();
26 24
27
28 const navigate = useNavigate(); 25 const navigate = useNavigate();
29 26
30 const [uploadRunContent, setUploadRunContent] = React.useState<UploadRunContent>({ 27 const [uploadRunContent, setUploadRunContent] = React.useState<UploadRunContent>({
@@ -125,44 +122,41 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
125 if (token) { 122 if (token) {
126 if (games[selectedGameID].is_coop) { 123 if (games[selectedGameID].is_coop) {
127 if (uploadRunContent.host_demo === null) { 124 if (uploadRunContent.host_demo === null) {
128 message("Error", "You must select a host demo to upload.") 125 await message("Error", "You must select a host demo to upload.")
129 return 126 return
130 } else if (uploadRunContent.partner_demo === null) { 127 } else if (uploadRunContent.partner_demo === null) {
131 message("Error", "You must select a partner demo to upload.") 128 await message("Error", "You must select a partner demo to upload.")
132 return 129 return
133 } 130 }
134 } else { 131 } else {
135 if (uploadRunContent.host_demo === null) { 132 if (uploadRunContent.host_demo === null) {
136 message("Error", "You must select a demo to upload.") 133 await message("Error", "You must select a demo to upload.")
137 return 134 return
138 } 135 }
139 } 136 }
140 const demo = SourceDemoParser.default() 137 const demo = SourceDemoParser.default()
141 .setOptions({ packets: true, header: true }) 138 .setOptions({ packets: true, header: true })
142 .parse(await uploadRunContent.host_demo.arrayBuffer()); 139 .parse(await uploadRunContent.host_demo.arrayBuffer());
143 const scoreboard = demo.findPacket<NetMessages.SvcUserMessage>((message) => { 140 const scoreboard = demo.findPacket<NetMessages.SvcUserMessage>((msg) => {
144 return message instanceof NetMessages.SvcUserMessage && message.userMessage instanceof ScoreboardTempUpdate; 141 return msg instanceof NetMessages.SvcUserMessage && msg.userMessage instanceof ScoreboardTempUpdate;
145 }) 142 })
146 143
147 if (!scoreboard) { 144 if (!scoreboard) {
148 message("Error", "Error while processing demo: Unable to get scoreboard result. Either there is a demo that is corrupt or haven't been recorded in challenge mode.") 145 await message("Error", "Error while processing demo: Unable to get scoreboard result. Either there is a demo that is corrupt or haven't been recorded in challenge mode.")
149 return 146 return
150 } 147 }
151 const { portalScore, timeScore } = scoreboard.userMessage?.as<ScoreboardTempUpdate>() ?? {}; 148 const { portalScore, timeScore } = scoreboard.userMessage?.as<ScoreboardTempUpdate>() ?? {};
152 console.log(`Map Name: ${demo.mapName}. Portal Count: ${portalScore}. Ticks: ${timeScore}.`);
153
154 setConfirmMessage(`Map Name: ${demo.mapName}\nPortal Count: ${portalScore}\nTicks: ${timeScore}\n\nAre you sure you want to upload this demo?`)
155 149
156 const userConfirmed = await confirm("Upload demo?", confirmMessage); 150 const userConfirmed = await confirm("Upload Record", `Map Name: ${demo.mapName}\nPortal Count: ${portalScore}\nTicks: ${timeScore}\n\nAre you sure you want to upload this demo?`);
157 151
158 if (!userConfirmed) { 152 if (!userConfirmed) {
159 return; 153 return;
160 } 154 }
161 155
162 const response = await API.post_record(token, uploadRunContent); 156 const [ success, response ] = await API.post_record(token, uploadRunContent);
163 await message("Message", response); 157 await message("Upload Record", response);
164 // navigate(0); 158 console.log("weweew")
165 onClose(); 159 onClose(success);
166 } 160 }
167 }; 161 };
168 162
@@ -242,7 +236,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
242 </div> 236 </div>
243 <div className='upload-run-buttons-container'> 237 <div className='upload-run-buttons-container'>
244 <button onClick={_upload_run}>Submit</button> 238 <button onClick={_upload_run}>Submit</button>
245 <button onClick={() => onClose()}>Cancel</button> 239 <button onClick={() => onClose(false)}>Cancel</button>
246 </div> 240 </div>
247 </div> 241 </div>
248 </> 242 </>
diff --git a/frontend/src/css/Dialog.css b/frontend/src/css/Dialog.css
index f16425d..47f070b 100644
--- a/frontend/src/css/Dialog.css
+++ b/frontend/src/css/Dialog.css
@@ -1,10 +1,9 @@
1.dimmer { 1.dimmer {
2 position: fixed; 2 position: fixed;
3 width: calc(100%); 3 width: 100%;
4 height: 100%; 4 height: 100%;
5 background-color: rgba(0, 0, 0, 0.5); 5 background-color: rgba(0, 0, 0, 0.5);
6 z-index: 4; 6 z-index: 4;
7 left: 0px;
8} 7}
9 8
10.dialog { 9.dialog {
diff --git a/frontend/src/css/UploadRunDialog.css b/frontend/src/css/UploadRunDialog.css
index b4667da..c783d2a 100644
--- a/frontend/src/css/UploadRunDialog.css
+++ b/frontend/src/css/UploadRunDialog.css
@@ -13,7 +13,7 @@ div#upload-run{
13 left: calc(50% + 160px); top: 130px; 13 left: calc(50% + 160px); top: 130px;
14 transform: translateX(-50%); 14 transform: translateX(-50%);
15 background-color: #2b2e46; 15 background-color: #2b2e46;
16 z-index: 2; color: white; 16 z-index: 3; color: white;
17 font-family: BarlowSemicondensed-SemiBold; 17 font-family: BarlowSemicondensed-SemiBold;
18} 18}
19 19
@@ -36,10 +36,9 @@ div#upload-run{
36 position: absolute; 36 position: absolute;
37 background-color: black; 37 background-color: black;
38 opacity: .3; 38 opacity: .3;
39 left: 320px; 39 width: 100%;
40 width: calc(100% - 320px);
41 height: 100%; 40 height: 100%;
42 z-index: 2; 41 z-index: 3;
43 cursor: no-drop; 42 cursor: no-drop;
44} 43}
45 44
diff --git a/frontend/src/hooks/UseConfirm.tsx b/frontend/src/hooks/UseConfirm.tsx
index 80a0d51..9a7853b 100644
--- a/frontend/src/hooks/UseConfirm.tsx
+++ b/frontend/src/hooks/UseConfirm.tsx
@@ -3,9 +3,9 @@ import ConfirmDialog from '../components/ConfirmDialog';
3 3
4const useConfirm = () => { 4const useConfirm = () => {
5 const [isOpen, setIsOpen] = useState(false); 5 const [isOpen, setIsOpen] = useState(false);
6 const [resolvePromise, setResolvePromise] = useState<((value: boolean) => void) | null>(null);
7 const [title, setTitle] = useState<string>(""); 6 const [title, setTitle] = useState<string>("");
8 const [subtitle, setSubtitle] = useState<string>(""); 7 const [subtitle, setSubtitle] = useState<string>("");
8 const [resolvePromise, setResolvePromise] = useState<((value: boolean) => void) | null>(null);
9 9
10 const confirm = ( titleN: string, subtitleN: string ) => { 10 const confirm = ( titleN: string, subtitleN: string ) => {
11 setIsOpen(true); 11 setIsOpen(true);
diff --git a/frontend/src/hooks/UseMessage.tsx b/frontend/src/hooks/UseMessage.tsx
index ebc4276..602cf65 100644
--- a/frontend/src/hooks/UseMessage.tsx
+++ b/frontend/src/hooks/UseMessage.tsx
@@ -3,10 +3,10 @@ import MessageDialog from "../components/MessageDialog";
3 3
4const useMessage = () => { 4const useMessage = () => {
5 const [isOpen, setIsOpen] = useState(false); 5 const [isOpen, setIsOpen] = useState(false);
6 const [resolvePromise, setResolvePromise] = useState<((value: boolean) => void) | null>(null);
7 6
8 const [title, setTitle] = useState<string>(""); 7 const [title, setTitle] = useState<string>("");
9 const [subtitle, setSubtitle] = useState<string>(""); 8 const [subtitle, setSubtitle] = useState<string>("");
9 const [resolvePromise, setResolvePromise] = useState<(() => void) | null>(null);
10 10
11 const message = (title: string, subtitle: string) => { 11 const message = (title: string, subtitle: string) => {
12 setIsOpen(true); 12 setIsOpen(true);
@@ -19,6 +19,10 @@ const useMessage = () => {
19 19
20 const handleClose = () => { 20 const handleClose = () => {
21 setIsOpen(false); 21 setIsOpen(false);
22 if (resolvePromise) {
23 resolvePromise();
24 setResolvePromise(null);
25 }
22 }; 26 };
23 27
24 const MessageDialogComponent = isOpen && ( 28 const MessageDialogComponent = isOpen && (
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 7559c77..5d1c75d 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -63,7 +63,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
63 }; 63 };
64 64
65 const _delete_submission = async (map_id: number, record_id: number) => { 65 const _delete_submission = async (map_id: number, record_id: number) => {
66 const userConfirmed = await confirm("Delete record?", "This action cannot be undone"); 66 const userConfirmed = await confirm("Delete Record", "Are you sure you want to delete this record?");
67 67
68 if (!userConfirmed) { 68 if (!userConfirmed) {
69 return; 69 return;
@@ -71,9 +71,10 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
71 71
72 const api_success = await API.delete_map_record(token!, map_id, record_id); 72 const api_success = await API.delete_map_record(token!, map_id, record_id);
73 if (api_success) { 73 if (api_success) {
74 message("Success", "Successfully deleted record"); 74 await message("Delete Record", "Successfully deleted record.");
75 onDeleteRecord();
75 } else { 76 } else {
76 message("Error", "Could not delete record"); 77 await message("Delete Record", "Could not delete record.");
77 } 78 }
78 }; 79 };
79 80