aboutsummaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/App.css25
-rw-r--r--frontend/src/components/ConfirmDialog.tsx5
-rw-r--r--frontend/src/components/GameCategory.tsx8
-rw-r--r--frontend/src/components/GameEntry.tsx10
-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.tsx16
-rw-r--r--frontend/src/css/Button.module.css91
-rw-r--r--frontend/src/css/Buttons.css3
-rw-r--r--frontend/src/css/Dialog.css5
-rw-r--r--frontend/src/css/Games.css6
-rw-r--r--frontend/src/css/Games.module.css61
-rw-r--r--frontend/src/css/Info.module.css21
-rw-r--r--frontend/src/css/Input.module.css15
-rw-r--r--frontend/src/css/Sidebar.module.css164
-rw-r--r--frontend/src/css/UploadRunDialog.css15
-rw-r--r--frontend/src/pages/About.tsx4
-rw-r--r--frontend/src/pages/Games.tsx31
-rw-r--r--frontend/src/pages/Maplist.tsx14
-rw-r--r--frontend/src/pages/Profile.tsx2
-rw-r--r--frontend/src/types/Sidebar.ts12
23 files changed, 799 insertions, 281 deletions
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 14a9972..a6ef415 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -1,13 +1,11 @@
1main { 1main {
2 overflow: auto; 2 overflow: auto;
3 overflow-x: hidden; 3 overflow-x: hidden;
4 position: relative;
5 4
6 width: calc(100% - 380px); 5 width: calc(100% - 350px);
7 height: 100vh; 6 height: 100vh;
8 left: 350px;
9 7
10 padding-right: 30px; 8 padding: 0px 30px;
11 9
12 font-size: 40px; 10 font-size: 40px;
13 font-family: BarlowSemiCondensed-Regular; 11 font-family: BarlowSemiCondensed-Regular;
@@ -15,9 +13,28 @@ main {
15 13
16} 14}
17 15
16button img {
17 height: 24px;
18}
19
20b {
21 font-family: BarlowCondensed-Bold;
22}
23
24* {
25 --text-color: #cdcfdf;
26 --primary: #2B2E46;
27 --primary-dark: #202232;
28}
29
30#root {
31 display: flex;
32}
33
18a { 34a {
19 color: inherit; 35 color: inherit;
20 width: fit-content; 36 width: fit-content;
37 text-decoration: none;
21} 38}
22 39
23body { 40body {
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/GameCategory.tsx b/frontend/src/components/GameCategory.tsx
index d8879ef..a568a8f 100644
--- a/frontend/src/components/GameCategory.tsx
+++ b/frontend/src/components/GameCategory.tsx
@@ -2,7 +2,7 @@ import React from 'react';
2import { Link } from "react-router-dom"; 2import { Link } from "react-router-dom";
3 3
4import { Game, GameCategoryPortals } from '@customTypes/Game'; 4import { Game, GameCategoryPortals } from '@customTypes/Game';
5import "@css/Games.css" 5import info from "@css/Info.module.css";
6 6
7interface GameCategoryProps { 7interface GameCategoryProps {
8 game: Game; 8 game: Game;
@@ -11,11 +11,11 @@ interface GameCategoryProps {
11 11
12const GameCategory: React.FC<GameCategoryProps> = ({cat, game}) => { 12const GameCategory: React.FC<GameCategoryProps> = ({cat, game}) => {
13 return ( 13 return (
14 <Link className="games-page-item-body-item" to={"/games/" + game.id + "?cat=" + cat.category.id}> 14 <Link className={info.infoBlock} to={"/games/" + game.id + "?cat=" + cat.category.id}>
15 <div> 15 <div>
16 <span className='games-page-item-body-item-title'>{cat.category.name}</span> 16 <span>{cat.category.name}</span>
17 <br /> 17 <br />
18 <span className='games-page-item-body-item-num'>{cat.portal_count}</span> 18 <span>{cat.portal_count}</span>
19 </div> 19 </div>
20 </Link> 20 </Link>
21 ) 21 )
diff --git a/frontend/src/components/GameEntry.tsx b/frontend/src/components/GameEntry.tsx
index 3bd2842..2fba85a 100644
--- a/frontend/src/components/GameEntry.tsx
+++ b/frontend/src/components/GameEntry.tsx
@@ -2,7 +2,7 @@ import React from 'react';
2import { Link } from "react-router-dom"; 2import { Link } from "react-router-dom";
3 3
4import { Game, GameCategoryPortals } from '@customTypes/Game'; 4import { Game, GameCategoryPortals } from '@customTypes/Game';
5import "@css/Games.css" 5import games from "@css/Games.module.css";
6 6
7import GameCategory from '@components/GameCategory'; 7import GameCategory from '@components/GameCategory';
8 8
@@ -18,12 +18,12 @@ const GameEntry: React.FC<GameEntryProps> = ({ game }) => {
18 }, [game.category_portals]); 18 }, [game.category_portals]);
19 19
20 return ( 20 return (
21 <Link to={"/games/" + game.id}><div className='games-page-item'> 21 <Link to={"/games/" + game.id}><div className={games.game}>
22 <div className='games-page-item-header'> 22 <div className={games.header}>
23 <div style={{ backgroundImage: `url(${game.image})` }} className='games-page-item-header-img'></div> 23 <div style={{ backgroundImage: `url(${game.image})` }}></div>
24 <span><b>{game.name}</b></span> 24 <span><b>{game.name}</b></span>
25 </div> 25 </div>
26 <div id={game.id as any as string} className='games-page-item-body'> 26 <div id={game.id as any as string} className={games.infoBlockContainer}>
27 {catInfo.map((cat, index) => { 27 {catInfo.map((cat, index) => {
28 return <GameCategory cat={cat} game={game} key={index}></GameCategory> 28 return <GameCategory cat={cat} game={game} key={index}></GameCategory>
29 })} 29 })}
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 c02fdb8..971a747 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 { API } from '@api/Api'; 8import { API } from '@api/Api';
@@ -226,7 +227,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
226 <span>Drag and drop</span> 227 <span>Drag and drop</span>
227 <div> 228 <div>
228 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br /> 229 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
229 <button style={{ borderRadius: "24px", padding: "5px 8px", margin: "5px 0px" }}>Upload</button> 230 <button className={btn.default}>Upload</button>
230 </div> 231 </div>
231 </div> 232 </div>
232 : null} 233 : null}
@@ -242,11 +243,8 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
242 <div onClick={() => { _handle_file_click(false) }} onDragOver={(e) => { _handle_drag_over(e, false) }} onDrop={(e) => { _handle_drop(e, false) }} onDragLeave={(e) => { _handle_drag_leave(e, false) }} className={`upload-run-drag-area ${dragHightlightPartner ? "upload-run-drag-area-highlight-partner" : ""} ${uploadRunContent.partner_demo ? "upload-run-drag-area-hidden" : ""}`}> 243 <div onClick={() => { _handle_file_click(false) }} onDragOver={(e) => { _handle_drag_over(e, false) }} onDrop={(e) => { _handle_drop(e, false) }} onDragLeave={(e) => { _handle_drag_leave(e, false) }} className={`upload-run-drag-area ${dragHightlightPartner ? "upload-run-drag-area-highlight-partner" : ""} ${uploadRunContent.partner_demo ? "upload-run-drag-area-hidden" : ""}`}>
243 <input ref={fileInputRefPartner} type="file" name="partner_demo" id="partner_demo" accept=".dem" onChange={(e) => _handle_file_change(e.target.files, false)} /> {!uploadRunContent.partner_demo ? 244 <input ref={fileInputRefPartner} type="file" name="partner_demo" id="partner_demo" accept=".dem" onChange={(e) => _handle_file_change(e.target.files, false)} /> {!uploadRunContent.partner_demo ?
244 <div> 245 <div>
245 <span>Drag and drop</span> 246 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
246 <div> 247 <button className={btn.default}>Upload</button>
247 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
248 <button style={{ borderRadius: "24px", padding: "5px 8px", margin: "5px 0px" }}>Upload</button>
249 </div>
250 </div> 248 </div>
251 : null} 249 : null}
252 250
@@ -265,9 +263,9 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
265 ) 263 )
266 } 264 }
267 </div> 265 </div>
268 <div className='upload-run-buttons-container'> 266 <div className='upload-run-buttons-container'>
269 <button onClick={_upload_run}>Submit</button> 267 <button className={`${btn.defaultWide}`} onClick={_upload_run}>Submit</button>
270 <button onClick={() => { 268 <button className={`${btn.defaultWide}`} onClick={() => {
271 onClose(false); 269 onClose(false);
272 setUploadRunContent({ 270 setUploadRunContent({
273 host_demo: null, 271 host_demo: null,
diff --git a/frontend/src/css/Button.module.css b/frontend/src/css/Button.module.css
new file mode 100644
index 0000000..d1c3ad7
--- /dev/null
+++ b/frontend/src/css/Button.module.css
@@ -0,0 +1,91 @@
1.default, .defaultWide, .sidebar, .logout {
2 border: none;
3 border-radius: 24px;
4 padding: 5px 10px;
5 background-color: #2b2e46;
6 font-family: BarlowSemiCondensed-Regular;
7 color: #CDCFDF;
8 font-size: 18px;
9 cursor: pointer;
10 transition: all 0.2s ease;
11}
12
13.sidebar.selected {
14 background-color: #202232;
15}
16
17.sidebar.selected:hover {
18 background-color: #202232;
19}
20
21.default:hover, .defaultWide:hover, .sidebar:hover {
22 background-color: rgb(38, 42, 62);
23}
24
25.defaultWide {
26 width: 100%;
27}
28
29.default.error, .defaultWide.error {
30 background-color: rgb(147, 45, 45);
31}
32
33.default.error:hover, .defaultWide.error {
34 background-color: rgb(105, 36, 36);
35}
36
37.sidebar {
38 display: flex;
39 width: 100%;
40 align-items: center;
41 font-family: BarlowSemiCondensed-Regular;
42 padding: 8px 12px;
43 height: 42px;
44 padding-right: 4px;
45 text-wrap: nowrap;
46 transition: all 0.2s ease;
47}
48
49.sidebar.min {
50 padding: 8px 9px;
51}
52
53.sidebar.min>span {
54 opacity: 0;
55 animation: sidebar_text_out 0.2s ease;
56 transform: translateX(-200px);
57}
58
59.sidebar img {
60 height: 24px;
61}
62
63.sidebar.profile>img {
64 border-radius: 24px;
65}
66
67.sidebar>span {
68 padding: 0px 8px;
69 transition: all 0.2s ease;
70}
71
72.logout {
73 background-color: #00000000;
74 display: flex;
75 align-items: center;
76}
77
78@keyframes sidebar_text_out {
79 0% {
80 opacity: 1;
81 transform: translateX(0px);
82 }
83 50% {
84 opacity: 0;
85 transform: translateX(0px);
86 }
87 60%, 100% {
88 transform: translateX(-200px);
89 }
90}
91
diff --git a/frontend/src/css/Buttons.css b/frontend/src/css/Buttons.css
new file mode 100644
index 0000000..de8f31d
--- /dev/null
+++ b/frontend/src/css/Buttons.css
@@ -0,0 +1,3 @@
1.default {
2
3}
diff --git a/frontend/src/css/Dialog.css b/frontend/src/css/Dialog.css
index fc557d2..51bf0ae 100644
--- a/frontend/src/css/Dialog.css
+++ b/frontend/src/css/Dialog.css
@@ -61,11 +61,6 @@
61 white-space: pre-wrap; 61 white-space: pre-wrap;
62} 62}
63 63
64.dialog-btns-container button {
65 border-radius: 24px;
66 padding: 5px 10px;
67}
68
69.dialog-btns-container button:nth-child(2):hover { 64.dialog-btns-container button:nth-child(2):hover {
70 background-color: rgb(105, 36, 36); 65 background-color: rgb(105, 36, 36);
71} 66}
diff --git a/frontend/src/css/Games.css b/frontend/src/css/Games.css
index ec57a71..9270a98 100644
--- a/frontend/src/css/Games.css
+++ b/frontend/src/css/Games.css
@@ -11,9 +11,7 @@
11} 11}
12 12
13.games-page-item-content { 13.games-page-item-content {
14 position: absolute; 14 position: relative;
15 left: 50px;
16 width: calc(100% - 100px);
17} 15}
18 16
19.games-page-item-content a { 17.games-page-item-content a {
@@ -96,4 +94,4 @@ span>b {
96.games-page-item-body-item-num { 94.games-page-item-body-item-num {
97 font-size: 50px; 95 font-size: 50px;
98 font-family: BarlowCondensed-Bold; 96 font-family: BarlowCondensed-Bold;
99} \ No newline at end of file 97}
diff --git a/frontend/src/css/Games.module.css b/frontend/src/css/Games.module.css
new file mode 100644
index 0000000..4c598cd
--- /dev/null
+++ b/frontend/src/css/Games.module.css
@@ -0,0 +1,61 @@
1.content {
2 position: relative;
3 display: flex;
4 flex-direction: column;
5 width: 100%;
6 gap: 24px;
7 margin-top: 24px;
8}
9
10.content a {
11 position: relative;
12 width: 100%;
13}
14
15.game {
16 display: flex;
17 width: 100%;
18 flex-direction: column;
19 overflow: hidden;
20 border-radius: 24px;
21 background-color: var(--primary-dark);
22}
23
24.header {
25 display: flex;
26 overflow: hidden;
27 position: relative;
28 flex: 1;
29}
30
31.header span {
32 position: relative;
33 z-index: 2;
34 display: flex;
35 text-align: center;
36 justify-content: center;
37 width: 100%;
38 padding: 24px 0px;
39 text-wrap: nowrap;
40}
41
42.header div {
43 position: absolute;
44 top: 0;
45 left: 0;
46 width: 100%;
47 height: 100%;
48 z-index: 1;
49 background-size: cover;
50 filter: blur(4px);
51}
52
53.infoBlockContainer {
54 flex: 1;
55 display: flex;
56 gap: 12px;
57 margin: 12px;
58 align-items: center;
59 justify-content: center;
60 height: 50%;
61}
diff --git a/frontend/src/css/Info.module.css b/frontend/src/css/Info.module.css
new file mode 100644
index 0000000..144346e
--- /dev/null
+++ b/frontend/src/css/Info.module.css
@@ -0,0 +1,21 @@
1.infoBlock {
2 background-color: var(--primary);
3 display: flex;
4 width: 100%;
5 border-radius: 18px;
6 text-align: center;
7 justify-content: center;
8 padding: 4px 0px;
9 text-wrap: nowrap;
10}
11
12.infoBlock > div > span:nth-child(1) {
13 margin-top: 0px;
14 font-size: 26px;
15}
16
17.infoBlock > div > span:nth-child(3) {
18 font-size: 50px;
19 font-family: BarlowCondensed-Bold;
20}
21
diff --git a/frontend/src/css/Input.module.css b/frontend/src/css/Input.module.css
new file mode 100644
index 0000000..c216f73
--- /dev/null
+++ b/frontend/src/css/Input.module.css
@@ -0,0 +1,15 @@
1.sidebar {
2 background-color: #161723;
3 color: var(--text-color);
4 border: none;
5 border-radius: 300px;
6 font-family: BarlowSemiCondensed-Regular;
7 padding: 8px;
8 width: calc(100% - 16px);
9 outline: none;
10 font-size: 18px;
11}
12
13.sidebar::placehoder {
14 color: #2b2e46;
15}
diff --git a/frontend/src/css/Sidebar.module.css b/frontend/src/css/Sidebar.module.css
new file mode 100644
index 0000000..356b062
--- /dev/null
+++ b/frontend/src/css/Sidebar.module.css
@@ -0,0 +1,164 @@
1.sidebar {
2 display: flex;
3 width: 350px;
4 min-width: 350px;
5 height: 100vh;
6 color: var(--text-color);
7 font-family: BarlowSemiCondensed-Regular;
8 font-size: 18px;
9 background-color: var(--primary-dark);
10 flex-direction: column;
11}
12
13.logo, .logo>a {
14 height: fit-content;
15 display: flex;
16 width: calc(100% - 4px);
17 text-decoration: none;
18 padding-left: 4px;
19 background-color: var(--primary)
20}
21
22.logo>a>img {
23 padding: 12px 8px;
24 height: 80px;
25}
26
27.logo>a>div {
28 display: flex;
29 flex-direction: column;
30 width: 100%;
31}
32
33.logo>a>div>span {
34 font-size: 36px;
35 font-family: BarlowCondensed-Regular;
36 padding-left: 8px;
37}
38
39.logoTitle b {
40 display: flex;
41 height: 60px;
42 font-size: 56px;
43}
44
45/* btns */
46.btns {
47 display: flex;
48 flex-direction: column;
49 padding: 0px 8px;
50 height: 100%;
51 width: calc(100% - 16px);
52 justify-content: space-between;
53 background-color: var(--primary);
54 transition: all 0.3s ease;
55}
56
57.topBtns>span {
58 height: 28px;
59 display: flex;
60}
61
62.btns.min {
63 width: 42px;
64}
65
66.topBtns, .bottomBtns {
67 display: flex;
68 flex-direction: column;
69 gap: 8px;
70}
71
72.topBtns {
73 padding-top: 8px;
74}
75
76.bottomBtns {
77 padding-bottom: 8px;
78}
79
80.topBtns a, .bottomBtns a {
81 width: 100%;
82}
83
84.btnsContainer {
85 display: flex;
86}
87
88/* Clusterfuck */
89.searchContainer {
90 width: 0%;
91 transition: all 0.3s ease, overflow-y 0.0s ease 0.1s;
92 opacity: 0;
93 overflow-y: hidden;
94}
95
96.searchContainer.min {
97 width: 100%;
98 transition: all 0.3s ease, opacity 0.1s ease 0.1s;
99 opacity: 1;
100 overflow-y: auto;
101}
102
103.inpContainer {
104 padding: 8px;
105}
106
107.searchResults {
108 overflow-y: auto;
109 height: calc(100% - 54px);
110}
111
112/* this can be improved */
113.result {
114 margin: 10px 6px 0 6px;
115 height: 80px;
116 width: 100%;
117 max-width: 285px;
118 animation: result_in 0.2s ease;
119 animation-fill-mode: backwards;
120 overflow: hidden;
121
122 border-radius: 20px;
123 text-align: center;
124
125 display: grid;
126
127 border: 0;
128 transition: background-color .1s;
129 background-color: #2b2e46;
130 grid-template-rows: 20% 20% 60%;
131 width: calc(100% - 15px);
132}
133.result>span{
134 color: #888;
135 font-size: 16px;
136 font-family: BarlowSemiCondensed-Regular;
137}
138.result>span:nth-child(3), .result.player span{
139 font-size: 30px;
140 color: #CDCFDF;
141}
142
143.result.player img {
144 height: 80px;
145}
146
147.result.player span {
148 display: flex;
149 text-align: left;
150 margin-left: 90px;
151 width: fit-content;
152}
153
154@keyframes result_in {
155 0% {
156 opacity: 0;
157 transform: translateY(20px);
158 }
159 100% {
160 opacity: 1;
161 transform: translateY(0px);
162 }
163}
164
diff --git a/frontend/src/css/UploadRunDialog.css b/frontend/src/css/UploadRunDialog.css
index f129bb8..7cc2cf5 100644
--- a/frontend/src/css/UploadRunDialog.css
+++ b/frontend/src/css/UploadRunDialog.css
@@ -96,25 +96,10 @@ div#upload-run{
96 } 96 }
97} 97}
98 98
99button, input {
100 background-color: #2b2e46;
101 border: none;
102 font-family: BarlowSemiCondensed-Regular;
103 color: #CDCFDF;
104 font-size: 18px;
105 cursor: pointer;
106 padding: 5px 0px;
107 transition: all 0.2s ease;
108}
109
110.upload-run-buttons-container button { 99.upload-run-buttons-container button {
111 border-radius: 32px; 100 border-radius: 32px;
112} 101}
113 102
114button:hover {
115 background-color: #222538;
116}
117
118.upload-run-map-container { 103.upload-run-map-container {
119 display: flex; 104 display: flex;
120} 105}
diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx
index a8b7826..b7bd534 100644
--- a/frontend/src/pages/About.tsx
+++ b/frontend/src/pages/About.tsx
@@ -28,12 +28,12 @@ const About: React.FC = () => {
28 28
29 29
30 return ( 30 return (
31 <div id="about"> 31 <main>
32 <Helmet> 32 <Helmet>
33 <title>LPHUB | About</title> 33 <title>LPHUB | About</title>
34 </Helmet> 34 </Helmet>
35 <ReactMarkdown>{aboutText}</ReactMarkdown> 35 <ReactMarkdown>{aboutText}</ReactMarkdown>
36 </div> 36 </main>
37 ); 37 );
38}; 38};
39 39
diff --git a/frontend/src/pages/Games.tsx b/frontend/src/pages/Games.tsx
index 15cc891..5e0d5bf 100644
--- a/frontend/src/pages/Games.tsx
+++ b/frontend/src/pages/Games.tsx
@@ -3,43 +3,26 @@ import { Helmet } from 'react-helmet';
3 3
4import GameEntry from '@components/GameEntry'; 4import GameEntry from '@components/GameEntry';
5import { Game } from '@customTypes/Game'; 5import { Game } from '@customTypes/Game';
6import "@css/Maps.css" 6import gamesCSS from "@css/Games.module.css";
7 7
8interface GamesProps { 8interface GamesProps {
9 games: Game[]; 9 games: Game[];
10} 10}
11 11
12const Games: React.FC<GamesProps> = ({ games }) => { 12const Games: React.FC<GamesProps> = ({ games }) => {
13
14 const _page_load = () => {
15 const loaders = document.querySelectorAll(".loader");
16 loaders.forEach((loader) => {
17 (loader as HTMLElement).style.display = "none";
18 });
19 }
20
21 React.useEffect(() => {
22 document.querySelectorAll(".games-page-item-body").forEach((game, index) => {
23 game.innerHTML = "";
24 });
25 _page_load();
26 }, []);
27
28 return ( 13 return (
29 <div className='games-page'> 14 <main>
30 <Helmet> 15 <Helmet>
31 <title>LPHUB | Games</title> 16 <title>LPHUB | Games</title>
32 </Helmet> 17 </Helmet>
33 <section> 18 <section>
34 <div className='games-page-content'> 19 <div className={gamesCSS.content}>
35 <div className='games-page-item-content'> 20 {games.map((game, index) => (
36 {games.map((game, index) => ( 21 <GameEntry game={game} key={index} />
37 <GameEntry game={game} key={index} /> 22 ))}
38 ))}
39 </div>
40 </div> 23 </div>
41 </section> 24 </section>
42 </div> 25 </main>
43 ); 26 );
44}; 27};
45 28
diff --git a/frontend/src/pages/Maplist.tsx b/frontend/src/pages/Maplist.tsx
index 76f9a52..b9e17f7 100644
--- a/frontend/src/pages/Maplist.tsx
+++ b/frontend/src/pages/Maplist.tsx
@@ -88,7 +88,13 @@ const Maplist: React.FC = () => {
88 } 88 }
89 }, [gameChapters]) 89 }, [gameChapters])
90 90
91 91 useEffect(() => {
92 const queryParams = new URLSearchParams(location.search);
93 const cat = queryParams.get("cat");
94 if (cat != null) {
95 setCatNum(parseFloat(cat) - 1);
96 }
97 }, [location])
92 98
93 return ( 99 return (
94 <main> 100 <main>
@@ -121,7 +127,9 @@ const Maplist: React.FC = () => {
121 )?.portal_count 127 )?.portal_count
122 } 128 }
123 </h2> 129 </h2>
124 <h3>portals</h3> 130 <h3>{game?.category_portals.find(
131 (obj) => obj.category.id === catNum + 1)!.portal_count == 1 ? "portal" : "portals"
132 }</h3>
125 </div> 133 </div>
126 <div className="game-header-categories"> 134 <div className="game-header-categories">
127 {game?.category_portals.map((cat, index) => ( 135 {game?.category_portals.map((cat, index) => (
@@ -160,7 +168,7 @@ const Maplist: React.FC = () => {
160 <span>{map.is_disabled ? map.category_portals[0].portal_count : map.category_portals.find( 168 <span>{map.is_disabled ? map.category_portals[0].portal_count : map.category_portals.find(
161 (obj) => obj.category.id === catNum + 1 169 (obj) => obj.category.id === catNum + 1
162 )?.portal_count}</span> 170 )?.portal_count}</span>
163 <span>portals</span> 171 <span>{map.category_portals.find((obj) => obj.category.id === catNum + 1)?.portal_count == 1 ? "portal" : "portals"}</span>
164 </div> 172 </div>
165 </div> 173 </div>
166 <div className="difficulty-bar"> 174 <div className="difficulty-bar">
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index 48233bf..ee56999 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -109,7 +109,7 @@ const Profile: React.FC<ProfileProps> = ({ profile, token, gameData, onDeleteRec
109 }; 109 };
110 110
111 return ( 111 return (
112 <div> 112 <div style={{position: "absolute", width: "calc(100% - 50px)", left: "350px"}}>
113 <Helmet> 113 <Helmet>
114 <title>LPHUB | {profile.user_name}</title> 114 <title>LPHUB | {profile.user_name}</title>
115 <meta name="description" content={profile.user_name} /> 115 <meta name="description" content={profile.user_name} />
diff --git a/frontend/src/types/Sidebar.ts b/frontend/src/types/Sidebar.ts
new file mode 100644
index 0000000..71a7571
--- /dev/null
+++ b/frontend/src/types/Sidebar.ts
@@ -0,0 +1,12 @@
1import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images';
2
3export interface Button {
4 img: string;
5 text: string;
6 url: string;
7}
8
9export interface Buttons {
10 top: Button[];
11 bottom: Button[];
12}