aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/ConfirmDialog.tsx5
-rw-r--r--frontend/src/components/Login.tsx38
-rw-r--r--frontend/src/components/MessageDialog.tsx3
-rw-r--r--frontend/src/components/Sidebar.tsx321
-rw-r--r--frontend/src/components/Sidebar_old.tsx210
-rw-r--r--frontend/src/components/UploadRunDialog.tsx9
6 files changed, 377 insertions, 209 deletions
diff --git a/frontend/src/components/ConfirmDialog.tsx b/frontend/src/components/ConfirmDialog.tsx
index 44a653b..0679c25 100644
--- a/frontend/src/components/ConfirmDialog.tsx
+++ b/frontend/src/components/ConfirmDialog.tsx
@@ -1,5 +1,6 @@
1import React from 'react'; 1import React from 'react';
2 2
3import btn from "@css/Button.module.css"
3import "@css/Dialog.css" 4import "@css/Dialog.css"
4 5
5interface ConfirmDialogProps { 6interface ConfirmDialogProps {
@@ -20,8 +21,8 @@ const ConfirmDialog: React.FC<ConfirmDialogProps> = ({ title, subtitle, onConfir
20 <span>{subtitle}</span> 21 <span>{subtitle}</span>
21 </div> 22 </div>
22 <div className='dialog-element dialog-btns-container'> 23 <div className='dialog-element dialog-btns-container'>
23 <button onClick={onCancel}>Cancel</button> 24 <button className={btn.default} onClick={onCancel}>Cancel</button>
24 <button onClick={onConfirm}>Confirm</button> 25 <button className={`${btn.default} ${btn.error}`} onClick={onConfirm}>Confirm</button>
25 </div> 26 </div>
26 </div> 27 </div>
27 </div> 28 </div>
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx
index f1628b2..fe0cbd1 100644
--- a/frontend/src/components/Login.tsx
+++ b/frontend/src/components/Login.tsx
@@ -5,14 +5,20 @@ import { ExitIcon, UserIcon, LoginIcon } from '@images/Images';
5import { UserProfile } from '@customTypes/Profile'; 5import { UserProfile } from '@customTypes/Profile';
6import { API } from '@api/Api'; 6import { API } from '@api/Api';
7import "@css/Login.css"; 7import "@css/Login.css";
8import { Button, Buttons } from "@customTypes/Sidebar";
9import btn from "@css/Button.module.css";
8 10
9interface LoginProps { 11interface LoginProps {
12 isSearching: boolean;
13 currentBtn: number;
14 buttonsList: Buttons;
15 setCurrentBtn: React.Dispatch<React.SetStateAction<number>>;
10 setToken: React.Dispatch<React.SetStateAction<string | undefined>>; 16 setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
11 profile?: UserProfile; 17 profile?: UserProfile;
12 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>; 18 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>;
13}; 19};
14 20
15const Login: React.FC<LoginProps> = ({ setToken, profile, setProfile }) => { 21const Login: React.FC<LoginProps> = ({ isSearching, currentBtn, buttonsList, setCurrentBtn, setToken, profile, setProfile }) => {
16 22
17 const navigate = useNavigate(); 23 const navigate = useNavigate();
18 24
@@ -36,13 +42,15 @@ const Login: React.FC<LoginProps> = ({ setToken, profile, setProfile }) => {
36 {profile.profile ? 42 {profile.profile ?
37 ( 43 (
38 <> 44 <>
39 <Link to="/profile" tabIndex={-1} className='login'> 45 <Link to="/profile" tabIndex={-1}>
40 <button className='sidebar-button'> 46 <button onClick={() => {setCurrentBtn(buttonsList.top.length)}} id="sidebarBtn" className={`${btn.sidebar} ${btn.profile} ${currentBtn == buttonsList.top.length ? btn.selected : ""} ${isSearching ? btn.min : ""}`}>
41 <img className="avatar-img" src={profile.avatar_link} alt="" /> 47 <img className="avatar-img" src={profile.avatar_link} alt="" />
48 <span style={{justifyContent: "space-between", display: "flex", alignItems: "center", width: "100%"}}>
42 <span>{profile.user_name}</span> 49 <span>{profile.user_name}</span>
43 </button> 50 <button className={btn.logout} onClick={_logout}>
44 <button className='logout-button' onClick={_logout}> 51 <img src={ExitIcon} alt="" /><span />
45 <img src={ExitIcon} alt="" /><span /> 52 </button>
53 </span>
46 </button> 54 </button>
47 </Link> 55 </Link>
48 </> 56 </>
@@ -50,8 +58,8 @@ const Login: React.FC<LoginProps> = ({ setToken, profile, setProfile }) => {
50 : 58 :
51 ( 59 (
52 <> 60 <>
53 <Link to="/" tabIndex={-1} className='login'> 61 <Link to="/" tabIndex={-1}>
54 <button className='sidebar-button'> 62 <button id="sidebarBtn" className={`${btn.sidebar} ${btn.profile} ${isSearching ? btn.min : ""}`}>
55 <img className="avatar-img" src={profile.avatar_link} alt="" /> 63 <img className="avatar-img" src={profile.avatar_link} alt="" />
56 <span>Loading Profile...</span> 64 <span>Loading Profile...</span>
57 </button> 65 </button>
@@ -66,12 +74,12 @@ const Login: React.FC<LoginProps> = ({ setToken, profile, setProfile }) => {
66 ) 74 )
67 : 75 :
68 ( 76 (
69 <Link to="/api/v1/login" tabIndex={-1} className='login' > 77 <Link to="/api/v1/login" tabIndex={-1}>
70 <button className='sidebar-button' onClick={_login}> 78 <button id="sidebarBtn" className={`${btn.sidebar} ${isSearching ? btn.min : ""}`} onClick={_login}>
71 <img className="avatar-img" src={UserIcon} alt="" /> 79 <img className="avatar-img" src={UserIcon} alt="" />
72 <span> 80 <span>
73 <img src={LoginIcon} alt="Sign in through Steam" /> 81 <img src={LoginIcon} alt="Sign in through Steam" />
74 </span> 82 </span>
75 </button> 83 </button>
76 </Link> 84 </Link>
77 )} 85 )}
diff --git a/frontend/src/components/MessageDialog.tsx b/frontend/src/components/MessageDialog.tsx
index 5c85189..8c584b7 100644
--- a/frontend/src/components/MessageDialog.tsx
+++ b/frontend/src/components/MessageDialog.tsx
@@ -1,5 +1,6 @@
1import React from 'react'; 1import React from 'react';
2 2
3import btn from "@css/Button.module.css"
3import "@css/Dialog.css" 4import "@css/Dialog.css"
4 5
5interface MessageDialogProps { 6interface MessageDialogProps {
@@ -19,7 +20,7 @@ const MessageDialog: React.FC<MessageDialogProps> = ({ title, subtitle, onClose
19 <span>{subtitle}</span> 20 <span>{subtitle}</span>
20 </div> 21 </div>
21 <div className='dialog-element dialog-btns-container'> 22 <div className='dialog-element dialog-btns-container'>
22 <button onClick={onClose}>Close</button> 23 <button className={btn.default} onClick={onClose}>Close</button>
23 </div> 24 </div>
24 </div> 25 </div>
25 </div> 26 </div>
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 67f7f3d..beff4f0 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1,12 +1,15 @@
1import React from 'react'; 1import React from "react";
2import { Link, useLocation } from 'react-router-dom'; 2import { Link, useLocation } from 'react-router-dom';
3
4import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; 3import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images';
5import Login from '@components/Login';
6import { UserProfile } from '@customTypes/Profile'; 4import { UserProfile } from '@customTypes/Profile';
7import { Search } from '@customTypes/Search'; 5import sidebar from "@css/Sidebar.module.css";
6import { Button, Buttons } from "@customTypes/Sidebar";
7import btn from "@css/Button.module.css";
8import { abort } from "process";
9import Login from "@components/Login";
8import { API } from '@api/Api'; 10import { API } from '@api/Api';
9import "@css/Sidebar.css"; 11import inp from "@css/Input.module.css";
12import { Search } from '@customTypes/Search';
10 13
11interface SidebarProps { 14interface SidebarProps {
12 setToken: React.Dispatch<React.SetStateAction<string | undefined>>; 15 setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
@@ -16,187 +19,131 @@ interface SidebarProps {
16}; 19};
17 20
18const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => { 21const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => {
19 22 const location = useLocation();
20 const [searchData, setSearchData] = React.useState<Search | undefined>(undefined); 23 const [load, setLoad] = React.useState<boolean>(false);
21 const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false); 24 const [searchData, setSearchData] = React.useState<Search | undefined>(undefined);
22 const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(true); 25 const [hasClickedSearch, setHasClickedSearch] = React.useState<boolean>(false);
23 26 const [isSearching, setIsSearching] = React.useState<boolean>(false);
24 const location = useLocation(); 27 const [buttonsList, setButtonsList] = React.useState<Buttons>({
25 const path = location.pathname; 28 top: [
26 29 {img: HomeIcon, text: "Home", url: "/"},
27 const handle_sidebar_click = (clicked_sidebar_idx: number) => { 30 {img: PortalIcon, text: "Games", url: "/games"},
28 const btn = document.querySelectorAll("button.sidebar-button"); 31 {img: FlagIcon, text: "Rankings", url: "/rankings"}
29 if (isSidebarOpen) { setSidebarOpen(false); _handle_sidebar_hide() } 32 ],
30 // clusterfuck 33 bottom: [
31 btn.forEach((e, i) => { 34 {img: BookIcon, text: "Rules", url: "/rules"},
32 btn[i].classList.remove("sidebar-button-selected") 35 {img: HelpIcon, text: "About LPHUB", url: "/about"}
33 btn[i].classList.add("sidebar-button-deselected") 36 ]
34 }) 37 });
35 btn[clicked_sidebar_idx].classList.add("sidebar-button-selected") 38
36 btn[clicked_sidebar_idx].classList.remove("sidebar-button-deselected") 39 const _handle_search = () => {
37 }; 40 if (!hasClickedSearch) {
38 41 _handle_search_change("");
39 const _handle_sidebar_hide = () => { 42 }
40 var btn = document.querySelectorAll("button.sidebar-button") as NodeListOf<HTMLElement> 43 setHasClickedSearch(true);
41 const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf<HTMLElement> 44 setIsSearching(!isSearching);
42 const side = document.querySelector("#sidebar-list") as HTMLElement; 45 document.querySelector<HTMLInputElement>("#searchInput")!.focus();
43 const searchbar = document.querySelector("#searchbar") as HTMLInputElement; 46 }
44 const uploadRunBtn = document.querySelector("#upload-run") as HTMLInputElement; 47
45 const uploadRunSpan = document.querySelector("#upload-run>span") as HTMLInputElement; 48 const _handle_search_change = async (query: string) => {
46 49 const response = await API.get_search(query);
47 if (isSidebarOpen) { 50 setSearchData(response);
48 if (profile) { 51 }
49 const login = document.querySelectorAll(".login>button")[1] as HTMLElement; 52
50 login.style.opacity = "1" 53 const _get_index_load = () => {
51 uploadRunBtn.style.width = "310px" 54 const pathname = window.location.pathname;
52 uploadRunBtn.style.padding = "0.4em 0 0 11px" 55 const btnObj = buttonsList.top.find(obj => obj.url === pathname);
53 uploadRunSpan.style.opacity = "0" 56 let btnIndex = buttonsList.top.findIndex(obj => obj.url === pathname);
54 setTimeout(() => { 57 if (btnIndex != -1) {
55 uploadRunSpan.style.opacity = "1" 58 return btnIndex;
56 }, 100) 59 } else if (buttonsList.top.findIndex(obj => obj.url === pathname) == -1 && buttonsList.bottom.findIndex(obj => obj.url === pathname) != -1) {
57 } 60 btnIndex = buttonsList.bottom.findIndex(obj => obj.url === pathname);
58 setSidebarOpen(false); 61 return btnIndex + buttonsList.top.length + 1;
59 side.style.width = "320px" 62 } else if (load) {
60 btn.forEach((e, i) => { 63 return currentBtn;
61 e.style.width = "310px" 64 } else {
62 e.style.padding = "0.4em 0 0 11px" 65 return 0;
63 setTimeout(() => { 66 }
64 span[i].style.opacity = "1" 67 }
65 }, 100) 68 const [currentBtn, setCurrentBtn] = React.useState<number>(_get_index_load);
66 }); 69
67 side.style.zIndex = "2" 70 React.useEffect(() => {
68 } else { 71 setCurrentBtn(_get_index_load);
69 if (profile) { 72 setLoad(true);
70 const login = document.querySelectorAll(".login>button")[1] as HTMLElement; 73 }, [location])
71 login.style.opacity = "0" 74
72 uploadRunBtn.style.width = "40px" 75 return (
73 uploadRunBtn.style.padding = "0.4em 0 0 5px" 76 <section className={sidebar.sidebar}>
74 uploadRunSpan.style.opacity = "0" 77 <div className={sidebar.logo}>
75 } 78 <Link onClick={isSearching ? _handle_search : () => {}} to={"/"}>
76 setSidebarOpen(true); 79 <img src={LogoIcon}/>
77 side.style.width = "40px"; 80 <div>
78 searchbar.focus(); 81 <span className={sidebar.logoTitle}><b>PORTAL 2</b></span>
79 btn.forEach((e, i) => { 82 <span>Least Portals Hub</span>
80 e.style.width = "40px" 83 </div>
81 e.style.padding = "0.4em 0 0 5px" 84 </Link>
82 span[i].style.opacity = "0" 85 </div>
83 }) 86
84 setTimeout(() => { 87 <div className={sidebar.btnsContainer} style={{height: "calc(100% - 104px)"}}>
85 side.style.zIndex = "0" 88 <div className={`${sidebar.btns} ${isSearching ? sidebar.min : ""}`}>
86 }, 300); 89 <div className={sidebar.topBtns}>
87 } 90 <button onClick={_handle_search} className={`${btn.sidebar} ${isSearching ? btn.min : ""}`}>
88 }; 91 <img src={SearchIcon}/>
89 92 <span>Search</span>
90 const _handle_sidebar_lock = () => { 93 </button>
91 if (!isSidebarLocked) { 94
92 _handle_sidebar_hide() 95 <span></span>
93 setIsSidebarLocked(true); 96
94 setTimeout(() => setIsSidebarLocked(false), 300); 97 {buttonsList.top.map((e: any, i: any) => {
95 } 98 return <Link to={e.url}><button onClick={isSearching ? _handle_search : () => {}} className={`${btn.sidebar} ${currentBtn == i ? btn.selected : ""} ${isSearching ? btn.min : ""}`} key={i}>
96 }; 99 <img src={e.img}/>
97 100 <span>{e.text}</span>
98 const _handle_search_change = async (q: string) => { 101 </button></Link>
99 const searchResponse = await API.get_search(q); 102 })
100 setSearchData(searchResponse); 103
101 }; 104 }
102 105 </div>
103 React.useEffect(() => { 106 <div className={sidebar.bottomBtns}>
104 if (path === "/") { handle_sidebar_click(1) } 107 <Login isSearching={isSearching} setCurrentBtn={setCurrentBtn} currentBtn={currentBtn} buttonsList={buttonsList} setToken={setToken} profile={profile} setProfile={setProfile}/>
105 else if (path.includes("games")) { handle_sidebar_click(2) } 108
106 else if (path.includes("rankings")) { handle_sidebar_click(3) } 109 {buttonsList.bottom.map((e: any, i: any) => {
107 // else if (path.includes("news")) { handle_sidebar_click(4) } 110 return <Link to={e.url}><button onClick={isSearching ? _handle_search : () => {}} key={i} className={`${btn.sidebar} ${currentBtn == i + buttonsList.top.length + 1 ? btn.selected : ""} ${isSearching ? btn.min : ""}`}>
108 // else if (path.includes("scorelog")) { handle_sidebar_click(5) } 111 <img src={e.img}/>
109 else if (path.includes("profile")) { handle_sidebar_click(4) } 112 <span>{e.text}</span>
110 else if (path.includes("rules")) { handle_sidebar_click(5) } 113 </button></Link>
111 else if (path.includes("about")) { handle_sidebar_click(6) } 114 })
112 }, [path]); 115
113 116 }
114 return ( 117 </div>
115 <div id='sidebar'> 118 </div>
116 <Link to="/" tabIndex={-1}> 119
117 <div id='logo'> {/* logo */} 120 <div className={`${sidebar.searchContainer} ${isSearching ? sidebar.min : ""}`}>
118 <img src={LogoIcon} alt="" height={"80px"} /> 121 <div className={sidebar.inpContainer}>
119 <div id='logo-text'> 122 <input onChange={(e) => {_handle_search_change(e.target.value)}} id="searchInput" className={inp.sidebar} type="text" placeholder='Search for map or a player...'/>
120 <span><b>PORTAL 2</b></span><br /> 123 </div>
121 <span>Least Portals Hub</span> 124
122 </div> 125 <div className={sidebar.searchResults}>
123 </div> 126 {searchData?.maps.map((map, i) => {
124 </Link> 127 return <Link style={{animationDelay: `${i < 30 ? i * 0.05 : 0}s`}} className={sidebar.result} to={`/maps/${map.id}`} key={i}>
125 <div id='sidebar-list'> {/* List */} 128 <span>{map.game}</span>
126 <div id='sidebar-toplist'> {/* Top */} 129 <span>{map.chapter}</span>
127 130 <span>{map.map}</span>
128 <button className='sidebar-button' onClick={() => _handle_sidebar_lock()}><img src={SearchIcon} alt="" /><span>Search</span></button> 131 </Link>
129 132 })}
130 <span></span> 133
131 134 {searchData?.players.map((player, i) => {
132 <Link to="/" tabIndex={-1}> 135 return <Link className={`${sidebar.result} ${sidebar.player}`} to={`/users/${player.steam_id}`}>
133 <button className='sidebar-button'><img src={HomeIcon} alt="homepage" /><span>Home&nbsp;Page</span></button> 136 <img src={player.avatar_link}/>
134 </Link> 137 <span>{player.user_name}</span>
135 138 </Link>
136 <Link to="/games" tabIndex={-1}> 139 })}
137 <button className='sidebar-button'><img src={PortalIcon} alt="games" /><span>Games</span></button> 140 </div>
138 </Link> 141 </div>
139 142
140 <Link to="/rankings" tabIndex={-1}> 143 </div>
141 <button className='sidebar-button'><img src={FlagIcon} alt="rankings" /><span>Rankings</span></button> 144 </section>
142 </Link> 145 )
143 146}
144 {/* <Link to="/news" tabIndex={-1}>
145 <button className='sidebar-button'><img src={NewsIcon} alt="news" /><span>News</span></button>
146 </Link> */}
147
148 {/* <Link to="/scorelog" tabIndex={-1}>
149 <button className='sidebar-button'><img src={TableIcon} alt="scorelogs" /><span>Score&nbsp;Logs</span></button>
150 </Link> */}
151 </div>
152 <div id='sidebar-bottomlist'>
153 <span></span>
154
155 {
156 profile && profile.profile ?
157 <button id='upload-run' className='submit-run-button' onClick={() => onUploadRun()}><img src={UploadIcon} alt="upload" /><span>Upload&nbsp;Record</span></button>
158 :
159 <span></span>
160 }
161
162 <Login setToken={setToken} profile={profile} setProfile={setProfile} />
163
164 <Link to="/rules" tabIndex={-1}>
165 <button className='sidebar-button'><img src={BookIcon} alt="rules" /><span>Leaderboard&nbsp;Rules</span></button>
166 </Link>
167
168 <Link to="/about" tabIndex={-1}>
169 <button className='sidebar-button'><img src={HelpIcon} alt="about" /><span>About&nbsp;LPHUB</span></button>
170 </Link>
171 </div>
172 </div>
173 <div>
174 <input type="text" id='searchbar' placeholder='Search for map or a player...' onChange={(e) => _handle_search_change(e.target.value)} />
175
176 <div id='search-data'>
177
178 {searchData?.maps.map((q, index) => (
179 <Link to={`/maps/${q.id}`} className='search-map' key={index}>
180 <span>{q.game}</span>
181 <span>{q.chapter}</span>
182 <span>{q.map}</span>
183 </Link>
184 ))}
185 {searchData?.players.map((q, index) =>
186 (
187 <Link to={
188 profile && q.steam_id === profile.steam_id ? `/profile` :
189 `/users/${q.steam_id}`
190 } className='search-player' key={index}>
191 <img src={q.avatar_link} alt='pfp'></img>
192 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>{q.user_name}</span>
193 </Link>
194 ))}
195
196 </div>
197 </div>
198 </div>
199 );
200};
201 147
202export default Sidebar; 148export default Sidebar;
149
diff --git a/frontend/src/components/Sidebar_old.tsx b/frontend/src/components/Sidebar_old.tsx
new file mode 100644
index 0000000..4d1cd7a
--- /dev/null
+++ b/frontend/src/components/Sidebar_old.tsx
@@ -0,0 +1,210 @@
1import React, { useRef } from 'react';
2import { Link, useLocation } from 'react-router-dom';
3
4import btn from "@css/Button.module.css";
5import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images';
6import Login from '@components/Login';
7import { UserProfile } from '@customTypes/Profile';
8import { Search } from '@customTypes/Search';
9import { API } from '@api/Api';
10import "@css/Sidebar.css";
11
12interface SidebarProps {
13 setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
14 profile?: UserProfile;
15 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>;
16 onUploadRun: () => void;
17};
18
19const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => {
20
21 const btnRef = useRef(null);
22 const [searchData, setSearchData] = React.useState<Search | undefined>(undefined);
23 const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false);
24 const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(true);
25
26 const location = useLocation();
27 const path = location.pathname;
28
29 const handle_sidebar_click = (clicked_sidebar_idx: number) => {
30 const btn = document.querySelectorAll("#sidebarBtn");
31 if (isSidebarOpen) { setSidebarOpen(false); _handle_sidebar_hide() }
32 // clusterfuck
33 btn.forEach((e, i) => {
34 btn[i].classList.remove("sidebar-button-selected")
35 btn[i].classList.add("sidebar-button-deselected")
36 })
37 btn[clicked_sidebar_idx].classList.add("sidebar-button-selected")
38 btn[clicked_sidebar_idx].classList.remove("sidebar-button-deselected")
39 };
40
41 const _handle_sidebar_hide = () => {
42 var btn = document.querySelectorAll("button.sidebar-button") as NodeListOf<HTMLElement>
43 const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf<HTMLElement>
44 const side = document.querySelector("#sidebar-list") as HTMLElement;
45 const searchbar = document.querySelector("#searchbar") as HTMLInputElement;
46 const uploadRunBtn = document.querySelector("#upload-run") as HTMLInputElement;
47 const uploadRunSpan = document.querySelector("#upload-run>span") as HTMLInputElement;
48
49 if (isSidebarOpen) {
50 if (profile) {
51 const login = document.querySelectorAll(".login>button")[1] as HTMLElement;
52 login.style.opacity = "1"
53 uploadRunBtn.style.width = "310px"
54 uploadRunBtn.style.padding = "0.4em 0 0 11px"
55 uploadRunSpan.style.opacity = "0"
56 setTimeout(() => {
57 uploadRunSpan.style.opacity = "1"
58 }, 100)
59 }
60 setSidebarOpen(false);
61 side.style.width = "320px"
62 btn.forEach((e, i) => {
63 e.style.width = "310px"
64 e.style.padding = "0.4em 0 0 11px"
65 setTimeout(() => {
66 span[i].style.opacity = "1"
67 }, 100)
68 });
69 side.style.zIndex = "2"
70 } else {
71 if (profile) {
72 const login = document.querySelectorAll(".login>button")[1] as HTMLElement;
73 login.style.opacity = "0"
74 uploadRunBtn.style.width = "40px"
75 uploadRunBtn.style.padding = "0.4em 0 0 5px"
76 uploadRunSpan.style.opacity = "0"
77 }
78 setSidebarOpen(true);
79 side.style.width = "40px";
80 searchbar.focus();
81 btn.forEach((e, i) => {
82 e.style.width = "40px"
83 e.style.padding = "0.4em 0 0 5px"
84 span[i].style.opacity = "0"
85 })
86 setTimeout(() => {
87 side.style.zIndex = "0"
88 }, 300);
89 }
90 };
91
92 const _handle_sidebar_lock = () => {
93 if (!isSidebarLocked) {
94 _handle_sidebar_hide()
95 setIsSidebarLocked(true);
96 setTimeout(() => setIsSidebarLocked(false), 300);
97 }
98 };
99
100 const _handle_search_change = async (q: string) => {
101 const searchResponse = await API.get_search(q);
102 setSearchData(searchResponse);
103 };
104
105 React.useEffect(() => {
106 if (path === "/") { handle_sidebar_click(1) }
107 else if (path.includes("games")) { handle_sidebar_click(2) }
108 else if (path.includes("rankings")) { handle_sidebar_click(3) }
109 // else if (path.includes("news")) { handle_sidebar_click(4) }
110 // else if (path.includes("scorelog")) { handle_sidebar_click(5) }
111 else if (path.includes("profile")) { handle_sidebar_click(4) }
112 else if (path.includes("rules")) { handle_sidebar_click(5) }
113 else if (path.includes("about")) { handle_sidebar_click(6) }
114 }, [path]);
115
116 React.useEffect(() => {
117 const btns = document.querySelectorAll("#sidebarBtn");
118 btns.forEach((e, num) => {
119 e.setAttribute("num", num.toString());
120 });
121 })
122
123 return (
124 <div id='sidebar'>
125 <Link to="/" tabIndex={-1}>
126 <div id='logo'> {/* logo */}
127 <img src={LogoIcon} alt="" height={"80px"} />
128 <div id='logo-text'>
129 <span><b>PORTAL 2</b></span><br />
130 <span>Least Portals Hub</span>
131 </div>
132 </div>
133 </Link>
134 <div id='sidebar-list'> {/* List */}
135 <div id='sidebar-toplist'> {/* Top */}
136
137 <button id="sidebarBtn" className='sidebar-button' onClick={() => _handle_sidebar_lock()}><img src={SearchIcon} alt="" /><span>Search</span></button>
138
139 <span></span>
140
141 <Link to="/" tabIndex={-1}>
142 <button ref={btnRef} id="sidebarBtn" className={`${btn.sidebar}`}><img src={HomeIcon} alt="homepage" /><span>Home&nbsp;Page</span></button>
143 </Link>
144
145 <Link to="/games" tabIndex={-1}>
146 <button id="sidebarBtn" className='sidebar-button'><img src={PortalIcon} alt="games" /><span>Games</span></button>
147 </Link>
148
149 <Link to="/rankings" tabIndex={-1}>
150 <button id="sidebarBtn" className='sidebar-button'><img src={FlagIcon} alt="rankings" /><span>Rankings</span></button>
151 </Link>
152
153 {/* <Link to="/news" tabIndex={-1}>
154 <button className='sidebar-button'><img src={NewsIcon} alt="news" /><span>News</span></button>
155 </Link> */}
156
157 {/* <Link to="/scorelog" tabIndex={-1}>
158 <button className='sidebar-button'><img src={TableIcon} alt="scorelogs" /><span>Score&nbsp;Logs</span></button>
159 </Link> */}
160 </div>
161 <div id='sidebar-bottomlist'>
162 <span></span>
163
164 {
165 profile && profile.profile ?
166 <button id='upload-run' className='submit-run-button' onClick={() => onUploadRun()}><img src={UploadIcon} alt="upload" /><span>Upload&nbsp;Record</span></button>
167 :
168 <span></span>
169 }
170
171
172 <Link to="/rules" tabIndex={-1}>
173 <button id="sidebarBtn" className='sidebar-button'><img src={BookIcon} alt="rules" /><span>Leaderboard&nbsp;Rules</span></button>
174 </Link>
175
176 <Link to="/about" tabIndex={-1}>
177 <button id="sidebarBtn" className='sidebar-button'><img src={HelpIcon} alt="about" /><span>About&nbsp;LPHUB</span></button>
178 </Link>
179 </div>
180 </div>
181 <div>
182 <input type="text" id='searchbar' placeholder='Search for map or a player...' onChange={(e) => _handle_search_change(e.target.value)} />
183
184 <div id='search-data'>
185
186 {searchData?.maps.map((q, index) => (
187 <Link to={`/maps/${q.id}`} className='search-map' key={index}>
188 <span>{q.game}</span>
189 <span>{q.chapter}</span>
190 <span>{q.map}</span>
191 </Link>
192 ))}
193 {searchData?.players.map((q, index) =>
194 (
195 <Link to={
196 profile && q.steam_id === profile.steam_id ? `/profile` :
197 `/users/${q.steam_id}`
198 } className='search-player' key={index}>
199 <img src={q.avatar_link} alt='pfp'></img>
200 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>{q.user_name}</span>
201 </Link>
202 ))}
203
204 </div>
205 </div>
206 </div>
207 );
208};
209
210export default Sidebar;
diff --git a/frontend/src/components/UploadRunDialog.tsx b/frontend/src/components/UploadRunDialog.tsx
index 118b589..951944b 100644
--- a/frontend/src/components/UploadRunDialog.tsx
+++ b/frontend/src/components/UploadRunDialog.tsx
@@ -2,6 +2,7 @@ import React from 'react';
2import { UploadRunContent } from '@customTypes/Content'; 2import { UploadRunContent } from '@customTypes/Content';
3import { ScoreboardTempUpdate, SourceDemoParser, NetMessages } from '@nekz/sdp'; 3import { ScoreboardTempUpdate, SourceDemoParser, NetMessages } from '@nekz/sdp';
4 4
5import btn from "@css/Button.module.css";
5import '@css/UploadRunDialog.css'; 6import '@css/UploadRunDialog.css';
6import { Game } from '@customTypes/Game'; 7import { Game } from '@customTypes/Game';
7import { Map } from '@customTypes/Map'; 8import { Map } from '@customTypes/Map';
@@ -241,7 +242,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
241 <span>Drag and drop</span> 242 <span>Drag and drop</span>
242 <div> 243 <div>
243 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br /> 244 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
244 <button style={{ borderRadius: "24px", padding: "5px 8px", margin: "5px 0px" }}>Upload</button> 245 <button className={btn.default}>Upload</button>
245 </div> 246 </div>
246 </div> 247 </div>
247 : null} 248 : null}
@@ -260,7 +261,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
260 <span>Drag and drop</span> 261 <span>Drag and drop</span>
261 <div> 262 <div>
262 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br /> 263 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
263 <button style={{ borderRadius: "24px", padding: "5px 8px", margin: "5px 0px" }}>Upload</button> 264 <button className={btn.default}>Upload</button>
264 </div> 265 </div>
265 </div> 266 </div>
266 : null} 267 : null}
@@ -281,8 +282,8 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
281 } 282 }
282 </div> 283 </div>
283 <div className='upload-run-buttons-container'> 284 <div className='upload-run-buttons-container'>
284 <button onClick={_upload_run}>Submit</button> 285 <button className={`${btn.defaultWide}`} onClick={_upload_run}>Submit</button>
285 <button onClick={() => onClose(false)}>Cancel</button> 286 <button className={`${btn.defaultWide}`} onClick={() => onClose(false)}>Cancel</button>
286 </div> 287 </div>
287 </div> 288 </div>
288 </div> 289 </div>