From 6a8b909afbe1560be95f7ad0a3e19cfe4717aec6 Mon Sep 17 00:00:00 2001
From: FifthWit <fifthwitbusiness@gmail.com>
Date: Thu, 14 Aug 2025 14:01:01 -0500
Subject: Switched to tailwind/vite

---
 frontend/src/components/Sidebar.tsx | 386 ++++++++++++++++++------------------
 1 file changed, 192 insertions(+), 194 deletions(-)

(limited to 'frontend/src/components/Sidebar.tsx')

diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index b55d56b..88a5297 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback } from "react";
+import React, { useCallback, useRef } from "react";
 import { Link, useLocation } from "react-router-dom";
 
 import {
@@ -10,12 +10,11 @@ import {
   PortalIcon,
   SearchIcon,
   UploadIcon,
-} from "@images/Images";
+} from "../images/Images";
 import Login from "@components/Login";
 import { UserProfile } from "@customTypes/Profile";
 import { Search } from "@customTypes/Search";
 import { API } from "@api/Api";
-import "@css/Sidebar.css";
 
 interface SidebarProps {
   setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
@@ -24,6 +23,17 @@ interface SidebarProps {
   onUploadRun: () => void;
 }
 
+function OpenSidebarIcon(){
+  return (
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-panel-right-close-icon lucide-panel-right-close"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m8 9 3 3-3 3"/></svg>
+  )
+}
+
+function ClosedSidebarIcon(){
+  return (
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-panel-right-open-icon lucide-panel-right-open"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/><path d="m10 15-3-3 3-3"/></svg>  )
+}
+
 const Sidebar: React.FC<SidebarProps> = ({
   setToken,
   profile,
@@ -34,100 +44,38 @@ const Sidebar: React.FC<SidebarProps> = ({
     undefined
   );
   const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false);
-  const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(true);
+  const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(false);
+  const [selectedButtonIndex, setSelectedButtonIndex] = React.useState<number>(1);
 
   const location = useLocation();
   const path = location.pathname;
 
-  const _handle_sidebar_hide = useCallback(() => {
-    var btn = document.querySelectorAll(
-      "button.sidebar-button"
-    ) as NodeListOf<HTMLElement>;
-    const span = document.querySelectorAll(
-      "button.sidebar-button>span"
-    ) as NodeListOf<HTMLElement>;
-    const side = document.querySelector("#sidebar-list") as HTMLElement;
-    const searchbar = document.querySelector("#searchbar") as HTMLInputElement;
-    const uploadRunBtn = document.querySelector(
-      "#upload-run"
-    ) as HTMLInputElement;
-    const uploadRunSpan = document.querySelector(
-      "#upload-run>span"
-    ) as HTMLInputElement;
+  const sidebarRef = useRef<HTMLDivElement>(null);
+  const searchbarRef = useRef<HTMLInputElement>(null);
+  const uploadRunRef = useRef<HTMLButtonElement>(null);
+  const sidebarButtonRefs = useRef<(HTMLButtonElement | null)[]>([]);
+
+  const _handle_sidebar_toggle = useCallback(() => {
+    if (!sidebarRef.current) return;
 
     if (isSidebarOpen) {
-      if (profile) {
-        const login = document.querySelectorAll(
-          ".login>button"
-        )[1] as HTMLElement;
-        login.style.opacity = "1";
-        uploadRunBtn.style.width = "310px";
-        uploadRunBtn.style.padding = "0.4em 0 0 11px";
-        uploadRunSpan.style.opacity = "0";
-        setTimeout(() => {
-          uploadRunSpan.style.opacity = "1";
-        }, 100);
-      }
       setSidebarOpen(false);
-      side.style.width = "320px";
-      btn.forEach((e, i) => {
-        e.style.width = "310px";
-        e.style.padding = "0.4em 0 0 11px";
-        setTimeout(() => {
-          span[i].style.opacity = "1";
-        }, 100);
-      });
-      side.style.zIndex = "2";
     } else {
-      if (profile) {
-        const login = document.querySelectorAll(
-          ".login>button"
-        )[1] as HTMLElement;
-        login.style.opacity = "0";
-        uploadRunBtn.style.width = "40px";
-        uploadRunBtn.style.padding = "0.4em 0 0 5px";
-        uploadRunSpan.style.opacity = "0";
-      }
       setSidebarOpen(true);
-      side.style.width = "40px";
-      searchbar.focus();
-      btn.forEach((e, i) => {
-        e.style.width = "40px";
-        e.style.padding = "0.4em 0 0 5px";
-        span[i].style.opacity = "0";
-      });
-      setTimeout(() => {
-        side.style.zIndex = "0";
-      }, 300);
+      searchbarRef.current?.focus();
     }
-  }, [isSidebarOpen, profile]);
+  }, [isSidebarOpen]);
 
   const handle_sidebar_click = useCallback(
     (clicked_sidebar_idx: number) => {
-      const btn = document.querySelectorAll("button.sidebar-button");
+      setSelectedButtonIndex(clicked_sidebar_idx);
       if (isSidebarOpen) {
         setSidebarOpen(false);
-        _handle_sidebar_hide();
       }
-      // clusterfuck
-      btn.forEach((e, i) => {
-        btn[i].classList.remove("sidebar-button-selected");
-        btn[i].classList.add("sidebar-button-deselected");
-      });
-      btn[clicked_sidebar_idx].classList.add("sidebar-button-selected");
-      btn[clicked_sidebar_idx].classList.remove("sidebar-button-deselected");
     },
-    [isSidebarOpen, _handle_sidebar_hide]
+    [isSidebarOpen]
   );
 
-  const _handle_sidebar_lock = () => {
-    if (!isSidebarLocked) {
-      _handle_sidebar_hide();
-      setIsSidebarLocked(true);
-      setTimeout(() => setIsSidebarLocked(false), 300);
-    }
-  };
-
   const _handle_search_change = async (q: string) => {
     const searchResponse = await API.get_search(q);
     setSearchData(searchResponse);
@@ -135,149 +83,199 @@ const Sidebar: React.FC<SidebarProps> = ({
 
   React.useEffect(() => {
     if (path === "/") {
-      handle_sidebar_click(1);
+      setSelectedButtonIndex(1);
     } else if (path.includes("games")) {
-      handle_sidebar_click(2);
+      setSelectedButtonIndex(2);
     } else if (path.includes("rankings")) {
-      handle_sidebar_click(3);
-    }
-    // else if (path.includes("news")) { handle_sidebar_click(4) }
-    // else if (path.includes("scorelog")) { handle_sidebar_click(5) }
-    else if (path.includes("profile")) {
-      handle_sidebar_click(4);
+      setSelectedButtonIndex(3);
+    } else if (path.includes("profile")) {
+      setSelectedButtonIndex(4);
     } else if (path.includes("rules")) {
-      handle_sidebar_click(5);
+      setSelectedButtonIndex(5);
     } else if (path.includes("about")) {
-      handle_sidebar_click(6);
+      setSelectedButtonIndex(6);
     }
-  }, [path, handle_sidebar_click]);
+  }, [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 (
-    <div id="sidebar">
-      <Link to="/" tabIndex={-1}>
-        <div id="logo">
-          {" "}
-          {/* logo */}
-          <img src={LogoIcon} alt="" height={"80px"} />
-          <div id="logo-text">
-            <span>
-              <b>PORTAL 2</b>
-            </span>
-            <br />
-            <span>Least Portals Hub</span>
+    <div className={`fixed top-0 left-0 h-screen bg-surface border-r border-border transition-all duration-300 z-10 overflow-hidden ${
+      isSidebarOpen ? 'w-80' : 'w-20'
+    }`}>
+      <div className="flex items-center h-20 px-4 border-b border-border">
+        <Link to="/" tabIndex={-1} className="flex items-center flex-1 cursor-pointer select-none min-w-0">
+          <img src={LogoIcon} alt="Logo" className="w-12 h-12 flex-shrink-0" />
+          {isSidebarOpen && (
+            <div className="ml-3 font-[--font-barlow-condensed-regular] text-white min-w-0 overflow-hidden">
+              <div className="font-[--font-barlow-condensed-bold] text-2xl leading-6 truncate">
+                PORTAL 2
+              </div>
+              <div className="text-sm leading-4 truncate">
+                Least Portals Hub
+              </div>
+            </div>
+          )}
+        </Link>
+        
+        <button
+          onClick={_handle_sidebar_toggle}
+          className="ml-2 p-2 rounded-lg hover:bg-surface1 transition-colors text-foreground"
+          title={isSidebarOpen ? "Close sidebar" : "Open sidebar"}
+        >
+          {isSidebarOpen ? <ClosedSidebarIcon /> : <OpenSidebarIcon />}
+        </button>
+      </div>
+
+      {/* Sidebar Content */}
+      <div 
+        ref={sidebarRef}
+        className="flex flex-col h-[calc(100vh-80px)] overflow-y-auto overflow-x-hidden"
+      >
+        {isSidebarOpen && (
+          <div className="p-4 border-b border-border min-w-0">
+            <div className="flex items-center gap-3 mb-3">
+              <img src={SearchIcon} alt="Search" className={iconClasses} />
+              <span className="text-white font-[--font-barlow-semicondensed-regular] truncate">Search</span>
+            </div>
+            
+            <div className="min-w-0">
+              <input
+                ref={searchbarRef}
+                type="text"
+                id="searchbar"
+                placeholder="Search for map or a player..."
+                onChange={e => _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 && (
+                <div className="mt-2 max-h-40 overflow-y-auto min-w-0">
+                  {searchData?.maps.map((q, index) => (
+                    <Link to={`/maps/${q.id}`} className="block p-2 mb-1 bg-surface1 rounded hover:bg-surface2 transition-colors min-w-0" key={index}>
+                      <span className="block text-xs text-subtext1 truncate">{q.game}</span>
+                      <span className="block text-xs text-subtext1 truncate">{q.chapter}</span>
+                      <span className="block text-sm text-foreground truncate">{q.map}</span>
+                    </Link>
+                  ))}
+                  {searchData?.players.map((q, index) => (
+                    <Link
+                      to={
+                        profile && q.steam_id === profile.steam_id
+                          ? `/profile`
+                          : `/users/${q.steam_id}`
+                      }
+                      className="flex items-center p-2 mb-1 bg-surface1 rounded hover:bg-surface2 transition-colors min-w-0"
+                      key={index}
+                    >
+                      <img src={q.avatar_link} alt="pfp" className="w-6 h-6 rounded-full mr-2 flex-shrink-0" />
+                      <span className="text-sm text-foreground truncate">
+                        {q.user_name}
+                      </span>
+                    </Link>
+                  ))}
+                </div>
+              )}
+            </div>
           </div>
+        )}
+
+        <div className="flex-1 p-4 min-w-0">
+          <nav className="space-y-2">
+            {[
+              {
+                to: "/",
+                refIndex: 1,
+                icon: HomeIcon,
+                alt: "Home",
+                label: "Home Page",
+              },
+              {
+                to: "/games",
+                refIndex: 2,
+                icon: PortalIcon,
+                alt: "Games",
+                label: "Games",
+              },
+              {
+                to: "/rankings",
+                refIndex: 3,
+                icon: FlagIcon,
+                alt: "Rankings",
+                label: "Rankings",
+              },
+            ].map(({ to, refIndex, icon, alt, label }) => (
+              <Link to={to} tabIndex={-1} key={refIndex}>
+                <button
+                  ref={el => sidebarButtonRefs.current[refIndex] = el}
+                  className={getButtonClasses(refIndex)}
+                  onClick={() => handle_sidebar_click(refIndex)}
+                >
+                  <img src={icon} alt={alt} className={iconClasses} />
+                  {isSidebarOpen && (
+                    <span className="text-white font-[--font-barlow-semicondensed-regular] truncate">
+                      {label}
+                    </span>
+                  )}
+                </button>
+              </Link>
+            ))}
+          </nav>
         </div>
-      </Link>
-      <div id="sidebar-list">
-        {" "}
-        {/* List */}
-        <div id="sidebar-toplist">
-          {" "}
-          {/* Top */}
-          <button
-            className="sidebar-button"
-            onClick={() => _handle_sidebar_lock()}
-          >
-            <img src={SearchIcon} alt="" />
-            <span>Search</span>
-          </button>
-          <span></span>
-          <Link to="/" tabIndex={-1}>
-            <button className="sidebar-button">
-              <img src={HomeIcon} alt="homepage" />
-              <span>Home&nbsp;Page</span>
-            </button>
-          </Link>
-          <Link to="/games" tabIndex={-1}>
-            <button className="sidebar-button">
-              <img src={PortalIcon} alt="games" />
-              <span>Games</span>
-            </button>
-          </Link>
-          <Link to="/rankings" tabIndex={-1}>
-            <button className="sidebar-button">
-              <img src={FlagIcon} alt="rankings" />
-              <span>Rankings</span>
-            </button>
-          </Link>
-          {/* <Link to="/news" tabIndex={-1}>
-            <button className='sidebar-button'><img src={NewsIcon} alt="news" /><span>News</span></button>
-          </Link> */}
-          {/* <Link to="/scorelog" tabIndex={-1}>
-            <button className='sidebar-button'><img src={TableIcon} alt="scorelogs" /><span>Score&nbsp;Logs</span></button>
-          </Link> */}
-        </div>
-        <div id="sidebar-bottomlist">
-          <span></span>
 
-          {profile && profile.profile ? (
+        {/* Bottom Section */}
+        <div className="p-4 border-t border-border space-y-2 min-w-0">
+          {profile && profile.profile && (
             <button
+              ref={uploadRunRef}
               id="upload-run"
-              className="submit-run-button"
+              className={getButtonClasses(-1)}
               onClick={() => onUploadRun()}
             >
-              <img src={UploadIcon} alt="upload" />
-              <span>Upload&nbsp;Record</span>
+              <img src={UploadIcon} alt="Upload" className={iconClasses} />
+              {isSidebarOpen && <span className="font-[--font-barlow-semicondensed-regular] truncate">Upload Record</span>}
             </button>
-          ) : (
-            <span></span>
           )}
 
-          <Login
-            setToken={setToken}
-            profile={profile}
-            setProfile={setProfile}
-          />
+          <div className={isSidebarOpen ? 'min-w-0' : 'flex justify-center'}>
+            <Login
+              setToken={setToken}
+              profile={profile}
+              setProfile={setProfile}
+              isOpen={isSidebarOpen}
+            />
+          </div>
 
           <Link to="/rules" tabIndex={-1}>
-            <button className="sidebar-button">
-              <img src={BookIcon} alt="rules" />
-              <span>Leaderboard&nbsp;Rules</span>
+            <button 
+              ref={el => sidebarButtonRefs.current[5] = el}
+              className={getButtonClasses(5)}
+              onClick={() => handle_sidebar_click(5)}
+            >
+              <img src={BookIcon} alt="Rules" className={iconClasses} />
+              {isSidebarOpen && <span className="font-[--font-barlow-semicondensed-regular] truncate">Leaderboard Rules</span>}
             </button>
           </Link>
 
           <Link to="/about" tabIndex={-1}>
-            <button className="sidebar-button">
-              <img src={HelpIcon} alt="about" />
-              <span>About&nbsp;LPHUB</span>
+            <button 
+              ref={el => sidebarButtonRefs.current[6] = el}
+              className={getButtonClasses(6)}
+              onClick={() => handle_sidebar_click(6)}
+            >
+              <img src={HelpIcon} alt="About" className={iconClasses} />
+              {isSidebarOpen && <span className="font-[--font-barlow-semicondensed-regular] truncate">About LPHUB</span>}
             </button>
           </Link>
         </div>
       </div>
-      <div>
-        <input
-          type="text"
-          id="searchbar"
-          placeholder="Search for map or a player..."
-          onChange={e => _handle_search_change(e.target.value)}
-        />
-
-        <div id="search-data">
-          {searchData?.maps.map((q, index) => (
-            <Link to={`/maps/${q.id}`} className="search-map" key={index}>
-              <span>{q.game}</span>
-              <span>{q.chapter}</span>
-              <span>{q.map}</span>
-            </Link>
-          ))}
-          {searchData?.players.map((q, index) => (
-            <Link
-              to={
-                profile && q.steam_id === profile.steam_id
-                  ? `/profile`
-                  : `/users/${q.steam_id}`
-              }
-              className="search-player"
-              key={index}
-            >
-              <img src={q.avatar_link} alt="pfp"></img>
-              <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>
-                {q.user_name}
-              </span>
-            </Link>
-          ))}
-        </div>
-      </div>
     </div>
   );
 };
-- 
cgit v1.2.3

