From da1fd74f9387149b2b94d62853587a8afdb74ddd Mon Sep 17 00:00:00 2001 From: Wolfboy248 Date: Thu, 21 Aug 2025 10:33:27 +0200 Subject: Reorganised Maplist and Sidebar --- frontend/src/App.css | 1 + frontend/src/App.tsx | 2 +- frontend/src/components/Sidebar.tsx | 288 --------------------- frontend/src/components/Sidebar/Content.tsx | 133 ++++++++++ frontend/src/components/Sidebar/Footer.tsx | 80 ++++++ frontend/src/components/Sidebar/Header.tsx | 26 ++ frontend/src/components/Sidebar/Sidebar.module.css | 23 ++ frontend/src/components/Sidebar/Sidebar.tsx | 101 ++++++++ frontend/src/pages/Maplist/Components/Map.tsx | 60 +++++ frontend/src/pages/Maplist/Maplist.tsx | 81 ++---- 10 files changed, 445 insertions(+), 350 deletions(-) delete mode 100644 frontend/src/components/Sidebar.tsx create mode 100644 frontend/src/components/Sidebar/Content.tsx create mode 100644 frontend/src/components/Sidebar/Footer.tsx create mode 100644 frontend/src/components/Sidebar/Header.tsx create mode 100644 frontend/src/components/Sidebar/Sidebar.module.css create mode 100644 frontend/src/components/Sidebar/Sidebar.tsx create mode 100644 frontend/src/pages/Maplist/Components/Map.tsx (limited to 'frontend') diff --git a/frontend/src/App.css b/frontend/src/App.css index a39dcf1..3e0d813 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -6,6 +6,7 @@ --color-main: #141520; --color-panel: #202232; --color-block: #2b2e46; + --color-bright: #333753; --color-white: #cdcfdf; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8a95e77..5d0c8eb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,7 +3,7 @@ import { Routes, Route } from "react-router-dom"; import { Helmet } from "react-helmet"; import { UserProfile } from "@customTypes/Profile"; -import Sidebar from "./components/Sidebar"; +import Sidebar from "./components/Sidebar/Sidebar"; import "./App.css"; import Profile from "@pages/Profile/Profile.tsx"; diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx deleted file mode 100644 index 0083a3e..0000000 --- a/frontend/src/components/Sidebar.tsx +++ /dev/null @@ -1,288 +0,0 @@ -import React, { useCallback, useRef } from "react"; -import { Link, useLocation } from "react-router-dom"; - -import { - BookIcon, - FlagIcon, - HelpIcon, - HomeIcon, - LogoIcon, - PortalIcon, - SearchIcon, - UploadIcon, -} from "../images/Images"; -import Login from "@components/Login"; -import { UserProfile } from "@customTypes/Profile"; -import { Search } from "@customTypes/Search"; -import { API } from "@api/Api"; - -interface SidebarProps { - setToken: React.Dispatch>; - profile?: UserProfile; - setProfile: React.Dispatch>; - onUploadRun: () => void; -} - -function OpenSidebarIcon() { - return ( - - ) -} - -function ClosedSidebarIcon() { - return ( - ) -} - -const Sidebar: React.FC = ({ - setToken, - profile, - setProfile, - onUploadRun, -}) => { - const [searchData, setSearchData] = React.useState( - undefined - ); - // const [isSidebarLocked, setIsSidebarLocked] = React.useState(false); - const [isSidebarOpen, setSidebarOpen] = React.useState(false); - const [selectedButtonIndex, setSelectedButtonIndex] = React.useState(1); - - const location = useLocation(); - const path = location.pathname; - - const sidebarRef = useRef(null); - const searchbarRef = useRef(null); - const uploadRunRef = useRef(null); - const sidebarButtonRefs = useRef<(HTMLButtonElement | null)[]>([]); - - const _handle_sidebar_toggle = useCallback(() => { - if (!sidebarRef.current) return; - - if (isSidebarOpen) { - setSidebarOpen(false); - } else { - setSidebarOpen(true); - searchbarRef.current?.focus(); - } - }, [isSidebarOpen]); - - const handle_sidebar_click = useCallback( - (clicked_sidebar_idx: number) => { - setSelectedButtonIndex(clicked_sidebar_idx); - if (isSidebarOpen) { - setSidebarOpen(false); - } - }, - [isSidebarOpen] - ); - - const _handle_search_change = async (q: string) => { - const searchResponse = await API.get_search(q); - setSearchData(searchResponse); - }; - - React.useEffect(() => { - if (path === "/") { - setSelectedButtonIndex(1); - } else if (path.includes("games")) { - setSelectedButtonIndex(2); - } else if (path.includes("rankings")) { - setSelectedButtonIndex(3); - } else if (path.includes("profile")) { - setSelectedButtonIndex(4); - } else if (path.includes("rules")) { - setSelectedButtonIndex(5); - } else if (path.includes("about")) { - setSelectedButtonIndex(6); - } - }, [path]); - - const getButtonClasses = (buttonIndex: number) => { - const baseClasses = "flex items-center gap-3 w-full text-left bg-inherit cursor-pointer border-none rounded-lg py-3 px-3 transition-all duration-300 hover:bg-surface1"; - const selectedClasses = selectedButtonIndex === buttonIndex ? "bg-primary text-background" : "bg-transparent text-foreground"; - - return `${baseClasses} ${selectedClasses}`; - }; - - const iconClasses = "w-6 h-6 flex-shrink-0"; - - return ( -
-
- - Logo - {isSidebarOpen && ( -
-
- PORTAL 2 -
-
- Least Portals Hub -
-
- )} - - - -
- - {/* Sidebar Content */} -
- {isSidebarOpen && ( -
-
- Search - Search -
- -
- _handle_search_change(e.target.value)} - className="w-full p-2 bg-input text-foreground border border-border rounded-lg text-sm min-w-0" - /> - - {searchData && ( -
- {searchData?.maps.map((q, index) => ( - - {q.game} - {q.chapter} - {q.map} - - ))} - {searchData?.players.map((q, index) => ( - - pfp - - {q.user_name} - - - ))} -
- )} -
-
- )} - -
- -
- - {/* Bottom Section */} -
- {profile && profile.profile && ( - - )} - -
- -
- - - - - - - - -
-
-
- ); -}; - -export default Sidebar; diff --git a/frontend/src/components/Sidebar/Content.tsx b/frontend/src/components/Sidebar/Content.tsx new file mode 100644 index 0000000..4051b08 --- /dev/null +++ b/frontend/src/components/Sidebar/Content.tsx @@ -0,0 +1,133 @@ +import React, { useRef } from "react"; +import { Link } from "react-router-dom"; +import { UserProfile } from "@customTypes/Profile"; +import { Search } from "@customTypes/Search"; +import { API } from "@api/Api"; + +import styles from "./Sidebar.module.css"; + +import { + FlagIcon, + HomeIcon, + PortalIcon, + SearchIcon, +} from "../../images/Images"; + +interface ContentProps { + profile?: UserProfile; + isSidebarOpen: boolean; + sidebarButtonRefs: React.RefObject<(HTMLButtonElement | null)[]>; + getButtonClasses: (buttonIndex: number) => string; + handle_sidebar_click: (clicked_sidebar_idx: number) => void; +}; + +const Content: React.FC = ({ profile, isSidebarOpen, sidebarButtonRefs, getButtonClasses, handle_sidebar_click }) => { + const [searchData, setSearchData] = React.useState( + undefined + ); + + const searchbarRef = useRef(null); + + const _handle_search_change = async (q: string) => { + const searchResponse = await API.get_search(q); + setSearchData(searchResponse); + }; + + const iconClasses = ""; + + return ( +
+ +
+
+ Search + Search +
+ +
+ _handle_search_change(e.target.value)} + className="w-full p-2 bg-input text-foreground border border-border rounded-lg text-sm min-w-0" + /> + + {searchData && ( +
+ {searchData?.maps.map((q, index) => ( + + {q.game} + {q.chapter} + {q.map} + + ))} + {searchData?.players.map((q, index) => ( + + pfp + + {q.user_name} + + + ))} +
+ )} +
+
+ +
+ +
+
+ ); +} + +export default Content; diff --git a/frontend/src/components/Sidebar/Footer.tsx b/frontend/src/components/Sidebar/Footer.tsx new file mode 100644 index 0000000..070301a --- /dev/null +++ b/frontend/src/components/Sidebar/Footer.tsx @@ -0,0 +1,80 @@ +import React, { useRef } from "react"; +import { Link } from "react-router-dom"; + +import styles from "./Sidebar.module.css"; + +import { UserProfile } from "@customTypes/Profile"; +import Login from "@components/Login"; + +import { + UploadIcon, + BookIcon, + HelpIcon, +} from "../../images/Images"; + +interface FooterProps { + profile?: UserProfile; + onUploadRun: () => void; + setProfile: React.Dispatch>; + setToken: React.Dispatch>; + sidebarButtonRefs: React.RefObject<(HTMLButtonElement | null)[]>; + getButtonClasses: (buttonIndex: number) => string; + handle_sidebar_click: (clicked_sidebar_idx: number) => void; +}; + +const Footer: React.FC = ({ profile, onUploadRun, setToken, setProfile, sidebarButtonRefs, getButtonClasses, handle_sidebar_click }) => { + const uploadRunRef = useRef(null); + + return ( +
+ {profile && profile.profile && ( + + )} + +
+ +
+ + + + + + + + +
+ ); +} + +export default Footer; diff --git a/frontend/src/components/Sidebar/Header.tsx b/frontend/src/components/Sidebar/Header.tsx new file mode 100644 index 0000000..e990060 --- /dev/null +++ b/frontend/src/components/Sidebar/Header.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +import { + LogoIcon, +} from "../../images/Images"; + +const Header: React.FC = () => { + return ( +
+ + Logo +
+
+ PORTAL 2 +
+
+ Least Portals Hub +
+
+ +
+ ) +} + +export default Header; diff --git a/frontend/src/components/Sidebar/Sidebar.module.css b/frontend/src/components/Sidebar/Sidebar.module.css new file mode 100644 index 0000000..8079676 --- /dev/null +++ b/frontend/src/components/Sidebar/Sidebar.module.css @@ -0,0 +1,23 @@ +.button { + display: flex; + width: 100%; + cursor: pointer; + align-items: center; + border-radius: 2000px; + transition: all 0.2s ease; + padding: 4px 0px; +} + +.button:hover { + background-color: var(--color-panel); +} + +.button>img { + padding: 0px 8px; + height: 28px; +} + +button>span { + font-size: 22px; + font-family: var(--font-barlow-semicondensed-regular); +} \ No newline at end of file diff --git a/frontend/src/components/Sidebar/Sidebar.tsx b/frontend/src/components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..2dafa2b --- /dev/null +++ b/frontend/src/components/Sidebar/Sidebar.tsx @@ -0,0 +1,101 @@ +import React, { useCallback, useRef } from "react"; +import { Link, useLocation } from "react-router-dom"; +import { UserProfile } from "@customTypes/Profile"; + +import Header from "./Header"; +import Footer from "./Footer"; +import Content from "./Content"; + +interface SidebarProps { + setToken: React.Dispatch>; + profile?: UserProfile; + setProfile: React.Dispatch>; + onUploadRun: () => void; +} + +const Sidebar: React.FC = ({ + setToken, + profile, + setProfile, + onUploadRun, +}) => { + // const [isSidebarLocked, setIsSidebarLocked] = React.useState(false); + const [isSidebarOpen, setSidebarOpen] = React.useState(false); + const [selectedButtonIndex, setSelectedButtonIndex] = React.useState(1); + + const location = useLocation(); + const path = location.pathname; + + const sidebarRef = useRef(null); + const sidebarButtonRefs = useRef<(HTMLButtonElement | null)[]>([]); + + // const _handle_sidebar_toggle = useCallback(() => { + // if (!sidebarRef.current) return; + + // if (isSidebarOpen) { + // setSidebarOpen(false); + // } else { + // setSidebarOpen(true); + // searchbarRef.current?.focus(); + // } + // }, [isSidebarOpen]); + + const handle_sidebar_click = useCallback( + (clicked_sidebar_idx: number) => { + setSelectedButtonIndex(clicked_sidebar_idx); + if (isSidebarOpen) { + setSidebarOpen(false); + } + }, + [isSidebarOpen] + ); + + React.useEffect(() => { + if (path === "/") { + setSelectedButtonIndex(1); + } else if (path.includes("games")) { + setSelectedButtonIndex(2); + } else if (path.includes("rankings")) { + setSelectedButtonIndex(3); + } else if (path.includes("profile")) { + setSelectedButtonIndex(4); + } else if (path.includes("rules")) { + setSelectedButtonIndex(5); + } else if (path.includes("about")) { + setSelectedButtonIndex(6); + } + }, [path]); + + const getButtonClasses = (buttonIndex: number) => { + const baseClasses = "flex items-center gap-3 w-full text-left bg-inherit cursor-pointer border-none rounded-[2000px] py-3 px-3 transition-all duration-300 hover:bg-panel"; + const selectedClasses = selectedButtonIndex === buttonIndex ? "bg-primary text-background" : "bg-transparent text-foreground"; + + return `${baseClasses} ${selectedClasses}`; + }; + + return ( +
+ + {/* Header */} +
+ +
+
+ {/* Sidebar Content */} + + + {/* Bottom Section */} +
+
+ +
+ +
+ +
+
+ ); +}; + +export default Sidebar; diff --git a/frontend/src/pages/Maplist/Components/Map.tsx b/frontend/src/pages/Maplist/Components/Map.tsx new file mode 100644 index 0000000..5451830 --- /dev/null +++ b/frontend/src/pages/Maplist/Components/Map.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import type { Map } from "@customTypes/Map"; + +interface MapProps { + map: Map; + catNum: number; +}; + +const Map: React.FC = ({ map, catNum }) => { + return ( + +
+ + + {map.name} + +
+
+ + {map.is_disabled + ? map.category_portals[0].portal_count + : map.category_portals.find( + obj => obj.category.id === catNum + 1 + )?.portal_count} + + + portals + +
+
+ +
+
+ {[1, 2, 3, 4, 5].map((point) => ( +
+ ))} +
+
+ +
+ ); +}; + +export default Map; diff --git a/frontend/src/pages/Maplist/Maplist.tsx b/frontend/src/pages/Maplist/Maplist.tsx index 8d9c14a..a5649db 100644 --- a/frontend/src/pages/Maplist/Maplist.tsx +++ b/frontend/src/pages/Maplist/Maplist.tsx @@ -6,6 +6,8 @@ import { API } from "@api/Api.ts"; import { Game } from "@customTypes/Game.ts"; import { GameChapter, GamesChapters } from "@customTypes/Chapters.ts"; +import Map from "./Components/Map"; + const Maplist: React.FC = () => { const [game, setGame] = React.useState(null); const [catNum, setCatNum] = React.useState(0); @@ -87,14 +89,14 @@ const Maplist: React.FC = () => { }, [gameChapters, location.search]); return ( -
+
LPHUB | Maplist -
+
- @@ -105,36 +107,36 @@ const Maplist: React.FC = () => {
) : (
-

+

{game?.name}

-
-

+
+ { game?.category_portals.find( obj => obj.category.id === catNum + 1 )?.portal_count } -

-

+ + portals -

+
-
+
{game?.category_portals.map((cat, index) => (
- \ +
{gameChapters?.chapters.map((chapter, i) => { return (
{ _fetch_chapters(chapter.id.toString()); _handle_dropdown_click(); @@ -189,50 +191,7 @@ const Maplist: React.FC = () => {
{curChapter?.maps.map((map, i) => { return ( -
- - - {map.name} - -
-
- - {map.is_disabled - ? map.category_portals[0].portal_count - : map.category_portals.find( - obj => obj.category.id === catNum + 1 - )?.portal_count} - - - portals - -
-
- -
-
- {[1, 2, 3, 4, 5].map((point) => ( -
- ))} -
-
- -
+ ); })}
-- cgit v1.2.3