diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ef8b103..282bb11 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -98,8 +98,8 @@ jobs:
push: false
tags: web-ui:test
- cypress-dashboard:
- name: Cypress - Dashboard
+ cypress-home:
+ name: Cypress - Home
runs-on: ubuntu-latest
steps:
@@ -118,7 +118,7 @@ jobs:
run: npx wait-on http://localhost:5173
- name: Run Cypress
- run: npx cypress run --spec "cypress/e2e/dashboard/**/*.cy.ts"
+ run: npx cypress run --spec "cypress/e2e/home/**/*.cy.ts"
cypress-events:
name: Cypress - Events
@@ -140,29 +140,7 @@ jobs:
run: npx wait-on http://localhost:5173
- name: Run Cypress
- run: npx cypress run --spec "cypress/e2e/dashboard/**/*.cy.ts"
-
- cypress-insights:
- name: Cypress - Insights
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Install dependencies
- run: npm ci
-
- - name: Build Docker image
- run: docker build -t web-ui:test .
-
- - name: Start container
- run: docker run -d --name webui -p 5173:80 web-ui:test
-
- - name: Wait for app
- run: npx wait-on http://localhost:5173
-
- - name: Run Cypress
- run: npx cypress run --spec "cypress/e2e/dashboard/**/*.cy.ts"
+ run: npx cypress run --spec "cypress/e2e/events/**/*.cy.ts"
cypress-organizations:
name: Cypress - Organizations
@@ -184,10 +162,10 @@ jobs:
run: npx wait-on http://localhost:5173
- name: Run Cypress
- run: npx cypress run --spec "cypress/e2e/dashboard/**/*.cy.ts"
+ run: npx cypress run --spec "cypress/e2e/organizations/**/*.cy.ts"
- cypress-user:
- name: Cypress - User
+ cypress-profile:
+ name: Cypress - Profile
runs-on: ubuntu-latest
steps:
@@ -206,4 +184,4 @@ jobs:
run: npx wait-on http://localhost:5173
- name: Run Cypress
- run: npx cypress run --spec "cypress/e2e/dashboard/**/*.cy.ts"
+ run: npx cypress run --spec "cypress/e2e/profile/**/*.cy.ts"
diff --git a/cypress/e2e/events/events.cy.ts b/cypress/e2e/events/events.cy.ts
index 336c993..46e8baf 100644
--- a/cypress/e2e/events/events.cy.ts
+++ b/cypress/e2e/events/events.cy.ts
@@ -2,6 +2,6 @@ describe('Event Page', () => {
it('displays the correct main title', () => {
cy.visit('localhost:5173/events');
- cy.get('h1').should('contain', 'My Events');
+ cy.get('h1').should('contain', 'Events');
});
});
diff --git a/cypress/e2e/dashboard/dashboard.cy.ts b/cypress/e2e/home/home.cy.ts
similarity index 52%
rename from cypress/e2e/dashboard/dashboard.cy.ts
rename to cypress/e2e/home/home.cy.ts
index b8957e8..1a9848f 100644
--- a/cypress/e2e/dashboard/dashboard.cy.ts
+++ b/cypress/e2e/home/home.cy.ts
@@ -1,7 +1,7 @@
-describe('Dashboard Page', () => {
+describe('Home Page', () => {
it('displays the correct main title', () => {
cy.visit('localhost:5173');
- cy.get('h1').should('contain', 'My Dashboard');
+ cy.get('h1').should('contain', 'Home');
});
});
diff --git a/cypress/e2e/insights/insights.cy.ts b/cypress/e2e/insights/insights.cy.ts
deleted file mode 100644
index 0a989b7..0000000
--- a/cypress/e2e/insights/insights.cy.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-describe('Insights Page', () => {
- it('displays the correct main title', () => {
- cy.visit('localhost:5173/insights');
-
- cy.get('h1').should('contain', 'Insights');
- });
-});
diff --git a/cypress/e2e/organizations/organizations.cy.ts b/cypress/e2e/organizations/organizations.cy.ts
index 874a36a..6597df7 100644
--- a/cypress/e2e/organizations/organizations.cy.ts
+++ b/cypress/e2e/organizations/organizations.cy.ts
@@ -2,6 +2,6 @@ describe('Organizations Page', () => {
it('displays the correct main title', () => {
cy.visit('localhost:5173/organizations');
- cy.get('h1').should('contain', 'My Organizations');
+ cy.get('h1').should('contain', 'Organizations');
});
});
diff --git a/cypress/e2e/profile/profile.cy.ts b/cypress/e2e/profile/profile.cy.ts
index e0adb53..b10553c 100644
--- a/cypress/e2e/profile/profile.cy.ts
+++ b/cypress/e2e/profile/profile.cy.ts
@@ -2,6 +2,6 @@ describe('Profile Page', () => {
it('displays the correct main title', () => {
cy.visit('localhost:5173/profile');
- cy.get('h1').should('contain', 'My Profile');
+ cy.get('h1').should('contain', 'Profile');
});
});
diff --git a/src/App.tsx b/src/App.tsx
index 2bbf2a4..9bbc7ad 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,7 +3,6 @@ import { ThemeProvider } from './contexts/ThemeContext';
import AppLayout from './components/AppLayout.tsx';
import Home from './pages/Home.tsx';
import Events from './pages/Events.tsx';
-import Insights from './pages/Insights.tsx';
import Organizations from './pages/Organizations.tsx';
import Testing from './pages/Testing.tsx'; // temp page
import Profile from './pages/Profile.tsx';
@@ -17,12 +16,11 @@ function App() {
}>
+ } />
} />
} />
- } />
} />
} />
- } />
diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx
index 5e57c80..d665fa8 100644
--- a/src/components/AppLayout.tsx
+++ b/src/components/AppLayout.tsx
@@ -1,24 +1,15 @@
/* Layout that each page will follow */
import '../css/app-layout.css';
import { Outlet } from 'react-router-dom';
-import { useCollapseSidebar } from '../hooks/useCollapseSidebar';
-import Sidebar from './sidebar/Sidebar.tsx';
+import Navbar from './navbar/Navbar.tsx';
export default function AppLayout() {
- const [collapsed, setCollapsed] = useCollapseSidebar('sidebar-collapsed', false);
-
- const toggleSidebar = () => {
- setCollapsed((prev) => !prev);
- };
-
return (
-
);
diff --git a/src/components/navbar/Navbar.tsx b/src/components/navbar/Navbar.tsx
new file mode 100644
index 0000000..1a28a92
--- /dev/null
+++ b/src/components/navbar/Navbar.tsx
@@ -0,0 +1,62 @@
+import { useLayoutEffect, useRef, useState, useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+import NavbarLink from './NavbarLink';
+import '../../css/navbar.css';
+
+type IndicatorState = { left: number; width: number };
+
+export default function Navbar() {
+ const navbarRef = useRef
(null);
+ const location = useLocation();
+ const [indicator, setIndicator] = useState({ left: 0, width: 0 });
+
+ const computeIndicator = () => {
+ const navbarEl = navbarRef.current;
+ if (!navbarEl) return;
+
+ const activeEl = navbarEl.querySelector('.navlink.active-navlink') as HTMLElement | null;
+ if (!activeEl) return;
+
+ const navRect = navbarEl.getBoundingClientRect();
+ const activeRect = activeEl.getBoundingClientRect();
+
+ const next = {
+ left: activeRect.left - navRect.left,
+ width: activeRect.width,
+ };
+
+ // Avoid useless state updates
+ setIndicator((prev) => (prev.left === next.left && prev.width === next.width ? prev : next));
+ };
+
+ // Run when the route changes (active link changes).
+ // useLayoutEffect helps avoid a visible "jump" on first render.
+ useLayoutEffect(() => {
+ // Let the DOM apply the active class first, then measure.
+ requestAnimationFrame(computeIndicator);
+ }, [location.pathname]);
+
+ // Also update on resize
+ useEffect(() => {
+ const onResize = () => computeIndicator();
+ window.addEventListener('resize', onResize);
+ return () => window.removeEventListener('resize', onResize);
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/src/components/navbar/NavbarLink.tsx b/src/components/navbar/NavbarLink.tsx
new file mode 100644
index 0000000..4ace121
--- /dev/null
+++ b/src/components/navbar/NavbarLink.tsx
@@ -0,0 +1,14 @@
+import { NavLink } from 'react-router-dom';
+
+type NavbarLinkProps = {
+ label: string;
+ nav: string;
+};
+
+export default function NavbarLink({ label, nav }: NavbarLinkProps) {
+ return (
+ `navlink ${isActive ? 'active-navlink' : ''}`}>
+ {label}
+
+ );
+}
diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx
deleted file mode 100644
index 47e3581..0000000
--- a/src/components/sidebar/Sidebar.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import '../../css/sidebar.css';
-import '../../css/login.css';
-import SidebarLink from './SidebarLink.tsx';
-import SidebarProfile from './SidebarProfile.tsx';
-import SidebarWorkspace from './SidebarWorkspace.tsx';
-
-import {
- AdjustmentsHorizontalIcon,
- Bars3Icon,
- HomeIcon,
- StarIcon,
- CalendarDaysIcon,
- UserGroupIcon,
- UserCircleIcon,
-} from '@heroicons/react/24/solid';
-
-type SidebarProps = {
- collapsed: boolean;
- onToggle: () => void;
-};
-
-export default function Sidebar({ collapsed, onToggle }: SidebarProps) {
- return (
- <>
-
-
-
- } nav="/" />
- } nav="/events" />
- } nav="/insights" />
- } nav="/organizations" />
- } nav="/testing" />
-
-
- } name="Kevin Smith" email="smithk@rpi.edu" />
-
-
- >
- );
-}
diff --git a/src/components/sidebar/SidebarGroup.tsx b/src/components/sidebar/SidebarGroup.tsx
deleted file mode 100644
index 9821c9f..0000000
--- a/src/components/sidebar/SidebarGroup.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-
-*** NOT IN USE ***
-Groups links allowing for a nested structure
-on the Sidebar.
-
-*/
-
-import { ChevronRightIcon, ChevronDownIcon } from '@heroicons/react/24/solid';
-import { useState, useEffect } from 'react';
-
-type SidebarGroupProps = {
- label: string;
- icon: React.ReactNode;
- children: React.ReactNode;
- collapsed: boolean;
-};
-
-export default function SidebarGroup({ label, icon, children, collapsed }: SidebarGroupProps) {
- const [open, setOpen] = useState(true);
- const Icon = open ? ChevronDownIcon : ChevronRightIcon;
-
- // This logic assumes the user wants groups back open when sidebar is toggled.
- useEffect(() => {
- if (collapsed) {
- // eslint-disable-next-line react-hooks/set-state-in-effect
- setOpen(false);
- } else {
- setOpen(true);
- }
- }, [collapsed]);
-
- return (
-
-
setOpen((prev) => !prev)}>
- {icon}
- {label}
-
-
- {open &&
{children}
}
-
- );
-}
diff --git a/src/components/sidebar/SidebarLink.tsx b/src/components/sidebar/SidebarLink.tsx
deleted file mode 100644
index a2c7981..0000000
--- a/src/components/sidebar/SidebarLink.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { NavLink } from 'react-router-dom';
-
-type SidebarLinkProps = {
- label: string;
- icon: React.ReactNode;
- nav: string;
-};
-
-export default function SidebarLink({ label, icon, nav }: SidebarLinkProps) {
- return (
- `sidebar-link ${isActive ? 'active-link' : ''}`}>
- {icon}
- {label}
-
- );
-}
diff --git a/src/components/sidebar/SidebarProfile.tsx b/src/components/sidebar/SidebarProfile.tsx
deleted file mode 100644
index 5757d79..0000000
--- a/src/components/sidebar/SidebarProfile.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useNavigate } from 'react-router-dom';
-
-type SidebarLinkProps = {
- icon: React.ReactNode;
- name: string;
- email: string;
-};
-
-export default function SidebarProfile({ icon, name, email }: SidebarLinkProps) {
- const navigate = useNavigate();
- return (
- navigate('/profile')}>
-
{icon}
-
- {name}
- {email}
-
-
- );
-}
diff --git a/src/components/sidebar/SidebarWorkspace.tsx b/src/components/sidebar/SidebarWorkspace.tsx
deleted file mode 100644
index 5fbddce..0000000
--- a/src/components/sidebar/SidebarWorkspace.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-
-Currently only used as a standard div, but supports
-a toggle for viewing links.
-
-*/
-
-import React from 'react';
-import { useState } from 'react';
-
-import { ChevronRightIcon, ChevronDownIcon } from '@heroicons/react/24/solid';
-
-interface SidebarWorkspaceProps {
- workspace?: string;
- children: React.ReactNode;
-}
-
-export default function SidebarWorkspace({ workspace, children }: SidebarWorkspaceProps) {
- const [open, setOpen] = useState(true);
- const Icon = open ? ChevronDownIcon : ChevronRightIcon;
-
- return (
-
-
setOpen((prev) => !prev)} className={`${workspace ? 'workspace-header' : 'hide'}`}>
-
- {workspace}
-
- {open && children}
-
- );
-}
diff --git a/src/css/app-layout.css b/src/css/app-layout.css
index 51c6cb1..2fc949e 100644
--- a/src/css/app-layout.css
+++ b/src/css/app-layout.css
@@ -3,13 +3,7 @@
width: 100vw;
display: flex;
flex-direction: column;
-}
-
-.app-sidebar-content-container {
- height: 100%;
- display: flex;
- flex-direction: row;
- flex: 1;
+ align-items: center;
}
.app-content {
diff --git a/src/css/navbar.css b/src/css/navbar.css
new file mode 100644
index 0000000..db244f5
--- /dev/null
+++ b/src/css/navbar.css
@@ -0,0 +1,39 @@
+.navbar {
+ position: relative;
+ display: inline-flex;
+ gap: 0.5rem;
+ background: #e6e6e6;
+ border-radius: 999px;
+ width: fit-content;
+ margin: 40px auto 0;
+}
+
+.navlink {
+ position: relative;
+ z-index: 2;
+ padding: 0.5rem 1rem;
+ border-radius: 999px;
+ text-decoration: none;
+ color: #444;
+}
+
+.navlink:hover {
+ background: rgb(0 0 0 / 5%);
+}
+
+.active-navlink {
+ color: #111;
+}
+
+.nav-indicator {
+ position: absolute;
+ z-index: 1;
+ left: 0;
+ height: 100%;
+ border-radius: 999px;
+ background: #d0d0d0;
+ transition:
+ transform 0.28s cubic-bezier(0.4, 0, 0.2, 1),
+ width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
+ will-change: transform, width;
+}
diff --git a/src/css/sidebar.css b/src/css/sidebar.css
deleted file mode 100644
index d6d9656..0000000
--- a/src/css/sidebar.css
+++ /dev/null
@@ -1,205 +0,0 @@
-/* Styles for Sidebar.tsx and SidebarButton.tsx */
-
-/* Main Sidebar */
-.sidebar {
- display: flex;
- flex-direction: column;
- width: 16rem;
- background: var(--sidebar-bg);
- padding: 0.5rem;
- transition: width 500ms ease;
- box-shadow: 3px 3px 6px rgb(0 0 0 / 15%);
-}
-
-.collapsed {
- width: 3.45rem;
-}
-
-.sidebar-header {
- display: flex;
- flex-direction: row;
- width: 100%;
- padding: 0.75rem 0;
- justify-content: center;
-}
-
-.toggle-spacer {
- width: 13.75rem;
- transition: width 500ms ease;
-}
-
-.sidebar.collapsed .toggle-spacer {
- width: 0;
-}
-
-.collapse-toggle {
- background: none;
- border: none;
- color: var(--standard-text);
- cursor: pointer;
-}
-
-.collapse-toggle:focus-visible {
- outline: none;
-}
-
-.collapse-toggle svg {
- height: 1.25rem;
- width: 1.25rem;
-}
-
-.sidebar-title {
- flex-grow: 1;
- font-size: 1.5rem;
-}
-
-.sidebar.collapsed .sidebar-title {
- display: none;
-}
-
-/* Sidebar Workspace */
-
-.sidebar-workspace {
- display: flex;
- flex-direction: column;
- gap: 0.4rem;
-}
-
-.sidebar-workspace:not(:last-child) {
- margin-bottom: 1rem;
-}
-
-.sidebar-workspace:last-child {
- margin-top: auto;
-}
-
-.workspace-icon {
- height: 0.85rem;
- width: 0.85rem;
- margin-left: 0.2rem;
- flex-shrink: 0;
-}
-
-.workspace-text {
- font-size: 0.85rem;
-}
-
-/* Sidebar Links */
-
-.sidebar-link,
-.workspace-header {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 0.7rem;
- justify-content: flex-start;
- width: 100%;
- border-radius: 0.5rem;
- padding: 0.4rem 0.6rem;
- cursor: pointer;
- text-decoration: none;
- color: var(--standard-text);
- overflow: hidden;
- white-space: nowrap;
- transition: none;
-}
-
-.sidebar-link:hover {
- background: var(--sidebar-link-hover-bg);
-}
-
-.active-link {
- background: var(--sidebar-active-link-bg) !important;
-}
-
-.sidebar-link-label {
- font-size: 1rem;
-}
-
-.sidebar-link-icon {
- width: 1.25rem;
- height: 1.25rem;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.sidebar-link-icon svg {
- width: 100%;
- height: 100%;
-}
-
-/* Sidebar Groups */
-
-.sidebar-group-header {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 0.7rem;
- justify-content: flex-start;
- width: 100%;
- border-radius: 0.5rem;
- padding: 0.4rem 0.6rem;
- cursor: pointer;
- text-decoration: none;
- color: var(--standard-text);
- overflow: hidden;
- white-space: nowrap;
- transition: none;
-}
-
-.group-collapse-icon {
- margin-left: auto;
- height: 1rem;
- width: 1rem;
-}
-
-.sidebar-group-children {
- display: flex;
- flex-direction: column;
- border-left: 1px solid var(--standard-text);
- margin-left: 1.225rem;
- padding-left: 1rem;
-}
-
-/* Sidebar Profile */
-
-.sidebar-profile-icon {
- width: 2rem;
- height: 2rem;
- flex-shrink: 0;
- transition:
- width 300ms ease,
- height 300ms ease;
-}
-
-.sidebar.collapsed .sidebar-profile-icon {
- width: 1.25rem;
- height: 1.25rem;
-}
-
-.sidebar-profile-content {
- display: flex;
- flex-direction: column;
-}
-
-.sidebar-profile-email {
- font-size: 0.75rem;
-}
-
-/* Transitions */
-
-.sidebar-label-transition {
- max-width: 200px;
- overflow: hidden;
- white-space: nowrap;
- transition:
- max-width 300ms ease,
- opacity 150ms ease 50ms;
-}
-
-.sidebar.collapsed .sidebar-label-transition {
- max-width: 0;
- opacity: 0;
-}
diff --git a/src/hooks/.gitkeep b/src/hooks/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/hooks/useCollapseSidebar.ts b/src/hooks/useCollapseSidebar.ts
deleted file mode 100644
index ae0f7bb..0000000
--- a/src/hooks/useCollapseSidebar.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { useEffect, useState } from 'react';
-
-export function useCollapseSidebar(
- key: string,
- defaultValue: boolean
-): [boolean, React.Dispatch>] {
- const [value, setValue] = useState(() => {
- if (typeof window === 'undefined') {
- return defaultValue;
- }
-
- const stored = localStorage.getItem(key);
- return stored !== null ? stored === 'true' : defaultValue;
- });
-
- useEffect(() => {
- if (typeof window !== 'undefined') {
- localStorage.setItem(key, String(value));
- }
- }, [key, value]);
-
- return [value, setValue];
-}
diff --git a/src/pages/Events.tsx b/src/pages/Events.tsx
index aedddef..d12f79d 100644
--- a/src/pages/Events.tsx
+++ b/src/pages/Events.tsx
@@ -2,7 +2,7 @@ export default function Events() {
return (
<>
-
My Events
+ Events
>
);
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index 17565c7..7758cfa 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -2,7 +2,7 @@ export default function Home() {
return (
<>
-
My Dashboard
+ Home
>
);
diff --git a/src/pages/Insights.tsx b/src/pages/Insights.tsx
deleted file mode 100644
index 101080e..0000000
--- a/src/pages/Insights.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-export default function Insights() {
- return (
- <>
-
-
Insights
-
- >
- );
-}
diff --git a/src/pages/Organizations.tsx b/src/pages/Organizations.tsx
index 905f43a..229f108 100644
--- a/src/pages/Organizations.tsx
+++ b/src/pages/Organizations.tsx
@@ -2,7 +2,7 @@ export default function Organizations() {
return (
<>
-
My Organizations
+ Organizations
>
);
diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx
index 4730ccc..b089692 100644
--- a/src/pages/Profile.tsx
+++ b/src/pages/Profile.tsx
@@ -2,7 +2,7 @@ export default function Profile() {
return (
<>
-
My Profile
+ Profile
>
);