Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/src/app/(main)/(landing)/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const Pricing = () => {
return (
<>
<main className="w-full overflow-hidden flex flex-col items-center justify-center relative">
<Header title="We are working on Opensox 2.0" />
<Header title="We are working on Opensox 2.0" homeLink="/dashboard/home" />
<div className="flex flex-col bg-[#151515]/20 backdrop-blur-xl relative w-full ">
<div className="h-full pv relative">
<div className=" py-8 border-b border-[#252525]">
Expand Down
65 changes: 42 additions & 23 deletions apps/web/src/components/dashboard/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

"use client";

import React, { useState, useEffect } from "react";
Expand All @@ -19,7 +20,7 @@ import {
Squares2X2Icon,
ChevronDownIcon,
LockClosedIcon,
AcademicCapIcon
AcademicCapIcon,
} from "@heroicons/react/24/outline";
import { useShowSidebar } from "@/store/useShowSidebar";
import { signOut, useSession } from "next-auth/react";
Expand All @@ -32,10 +33,9 @@ type RouteConfig = {
path: string;
label: string;
icon: React.ReactNode;
badge?: string; // optional badge text (e.g., "New", "Beta")
badge?: string;
};

// free features only
const FREE_ROUTES: RouteConfig[] = [
{
path: "/dashboard/home",
Expand All @@ -59,7 +59,6 @@ const FREE_ROUTES: RouteConfig[] = [
},
];

// premium features under Opensox Pro
const PREMIUM_ROUTES: RouteConfig[] = [
{
path: "/dashboard/pro/dashboard",
Expand All @@ -82,6 +81,9 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
const { isPaidUser } = useSubscription();
const [proSectionExpanded, setProSectionExpanded] = useState(true);

// 🟣 New state for hover expand
const [isHovered, setIsHovered] = useState(false);

Comment on lines +84 to +86
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

tighten hover expand logic to avoid flicker and align comments with guidelines

the new hover-based expand logic is a nice ux improvement, but the current setTimeout on mouse leave can cause the sidebar to collapse even when the pointer has re-entered (the earlier timeout still fires and flips isHovered back to false). the emoji/capitalized comments also violate the “lowercase, why-not-what” comment guideline.

-  // 🟣 New state for hover expand
-  const [isHovered, setIsHovered] = useState(false);
+  // hover expand state
+  const [isHovered, setIsHovered] = useState(false);
+  const hoverTimeoutRef = React.useRef<number | null>(null);
@@
-  // 🟣 Hover handlers
-  const handleMouseEnter = () => {
-    if (isCollapsed) setIsHovered(true);
-  };
-
-  const handleMouseLeave = () => {
-    if (isHovered) setTimeout(() => setIsHovered(false), 150);
-  };
+  // hover handlers
+  const handleMouseEnter = () => {
+    if (hoverTimeoutRef.current !== null) {
+      window.clearTimeout(hoverTimeoutRef.current);
+      hoverTimeoutRef.current = null;
+    }
+    if (isCollapsed) {
+      setIsHovered(true);
+    }
+  };
+
+  const handleMouseLeave = () => {
+    if (isCollapsed && isHovered) {
+      hoverTimeoutRef.current = window.setTimeout(() => {
+        setIsHovered(false);
+        hoverTimeoutRef.current = null;
+      }, 150);
+    }
+  };

this keeps the small delay but prevents stale timeouts from collapsing the sidebar while hovered, and brings the new comments in line with the lowercase/comment-style guidelines. as per coding guidelines, comments should be minimal, lowercase, and explain intent rather than restating behavior.

Also applies to: 111-119, 120-123, 130-137

🤖 Prompt for AI Agents
In apps/web/src/components/dashboard/Sidebar.tsx around lines 84-86 (also apply
to 111-119, 120-123, 130-137): the hover-expand logic currently uses a
setTimeout on mouse leave that can fire after the pointer has re-entered,
causing flicker; replace this by storing the timeout id in a ref,
clearTimeout(ref.current) on mouse enter (and before creating a new timeout),
assign the id to ref.current when scheduling the collapse on mouse leave, and
clear it on unmount so stale timers cannot flip isHovered back to false; also
update the inline comments to be lowercase and minimal intent-focused (no
emoji/capitalized restatements).

// auto-expand pro section if user is on a premium route
useEffect(() => {
if (isPaidUser) {
Expand All @@ -106,19 +108,33 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
}
};

const desktopWidth = isCollapsed ? 80 : 288;
// 🟣 Hover handlers
const handleMouseEnter = () => {
if (isCollapsed) setIsHovered(true);
};

const handleMouseLeave = () => {
if (isHovered) setTimeout(() => setIsHovered(false), 150);
};

// compute dynamic width
const isSidebarExpanded = !isCollapsed || isHovered;
const desktopWidth = isSidebarExpanded ? 288 : 80;
const mobileWidth = desktopWidth;

return (
<motion.div
className={`h-screen flex flex-col bg-dash-surface border-r border-dash-border z-50 ${
overlay ? "fixed left-0 top-0 bottom-0 xl:hidden" : ""
}`}
initial={
overlay ? { x: -400, width: mobileWidth } : { width: desktopWidth }
}
animate={overlay ? { x: 0, width: mobileWidth } : { width: desktopWidth }}
exit={overlay ? { x: -400, width: mobileWidth } : undefined}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
initial={{
width: overlay ? mobileWidth : desktopWidth,
}}
animate={{
width: overlay ? mobileWidth : desktopWidth,
}}
transition={{ type: "spring", stiffness: 260, damping: 30 }}
style={{ width: overlay ? mobileWidth : desktopWidth }}
>
Expand All @@ -139,7 +155,7 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {

{/* Desktop header with collapse */}
<div className="hidden xl:flex items-center justify-between px-4 py-4 border-b border-dash-border bg-dash-surface">
{!isCollapsed && (
{!isSidebarExpanded ? null : (
<Link
href="/"
className="text-text-secondary font-semibold tracking-wide select-none text-xl hover:text-brand-purple transition-colors cursor-pointer"
Expand All @@ -149,17 +165,18 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
)}
<IconWrapper
onClick={toggleCollapsed}
className={isCollapsed ? "w-full flex justify-center" : ""}
className={isSidebarExpanded ? "" : "w-full flex justify-center"}
>
{isCollapsed ? (
<ChevronRightIcon className="size-5 text-brand-purple" />
) : (
{isSidebarExpanded ? (
<ChevronLeftIcon className="size-5 text-brand-purple" />
) : (
<ChevronRightIcon className="size-5 text-brand-purple" />
)}
</IconWrapper>
</div>

<div className="sidebar-body flex-grow flex-col overflow-y-auto px-3 py-4 space-y-1">
{/* Sidebar content */}
<div className="sidebar-body flex-grow flex-col overflow-y-auto px-3 py-4 space-y-1 transition-all duration-300">
{/* free features section */}
{FREE_ROUTES.map((route) => {
const isActive =
Expand All @@ -182,7 +199,7 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
>
{route.icon}
</span>
{!isCollapsed && (
{isSidebarExpanded && (
<div className="flex items-center gap-1.5 flex-1 min-w-0">
<h1
className={`text-xs font-medium transition-colors ${
Expand All @@ -206,14 +223,14 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
})}

{/* divider */}
{!isCollapsed && (
{isSidebarExpanded && (
<div className="my-3 px-3">
<div className="border-t border-dash-border" />
</div>
)}

{/* premium section */}
{!isCollapsed ? (
{isSidebarExpanded ? (
<div className="space-y-1">
{(() => {
const isPremiumRouteActive = PREMIUM_ROUTES.some(
Expand Down Expand Up @@ -401,7 +418,7 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
)}

{/* divider */}
{!isCollapsed && (
{isSidebarExpanded && (
<div className="my-3 px-3">
<div className="border-t border-dash-border" />
</div>
Expand All @@ -412,12 +429,12 @@ export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
itemName="Request a feature"
onclick={reqFeatureHandler}
icon={<SparklesIcon className="size-5" />}
collapsed={isCollapsed}
collapsed={!isSidebarExpanded}
/>
</div>

{/* Bottom profile */}
<ProfileMenu isCollapsed={isCollapsed} />
<ProfileMenu isCollapsed={!isSidebarExpanded} />
</motion.div>
);
}
Expand Down Expand Up @@ -466,7 +483,9 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) {
<span className="text-[10px] text-text-muted">{userEmail}</span>
</div>
<ChevronLeftIcon
className={`size-4 text-text-muted transition-transform ${open ? "rotate-90" : "-rotate-90"}`}
className={`size-4 text-text-muted transition-transform ${
open ? "rotate-90" : "-rotate-90"
}`}
/>
</div>
)}
Expand Down
28 changes: 22 additions & 6 deletions apps/web/src/components/ui/header.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
'use client'

import { motion } from 'framer-motion'
import React from 'react'
import { FlickeringGrid } from './flickering-grid'
import { colors } from '@/lib/design-tokens'
import Link from 'next/link'
import { Home } from 'lucide-react'

const Header = ({title}: {title: string}) => {
const Header = ({ title, homeLink }: { title: string; homeLink?: string }) => {
return (
<div className="px-[30px] py-10 h-[160px] relative overflow-hidden border-b border w-full">
<div className="px-[30px] py-10 h-[160px] relative overflow-hidden border-b border-white/10 w-full">

{homeLink && (
<Link
href={homeLink}
aria-label="go to dashboard home"
className="absolute left-2 top-4 lg:left-8 lg:top-8 z-40 p-1.5 lg:p-2 bg-white/10 hover:bg-white/20 backdrop-blur-md border border-white/10 rounded-full transition-all duration-200 group"
>
<Home className="w-4 h-4 lg:w-5 lg:h-5 text-white/70 group-hover:text-white" />
</Link>
)}

<motion.h4
initial={{ opacity: 0, y: 30, filter: 'blur(10px)' }}
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
transition={{ duration: 0.6, ease: 'easeOut', type: 'spring', delay: 0.3 }}
className='font-medium inset-0 flex items-center justify-center text-3xl lg:text-5xl tracking-tight absolute z-30 text-center text-balance'
className="font-medium inset-0 flex items-center justify-center text-3xl lg:text-5xl tracking-tight absolute z-30 text-center"
>
{title}
</motion.h4>

<div
style={{
background: 'radial-gradient(circle at center, #101010 30%, transparent 100%)',
}}
className="h-full w-[100%] right-0 top-0 z-20 absolute"
className="h-full w-full right-0 top-0 z-20 absolute"
/>
<div className="absolute right-0 w-[100%] h-full top-0 z-10 opacity-50">

<div className="absolute right-0 w-full h-full top-0 z-10 opacity-50">
<FlickeringGrid
className="absolute -z-0 top-0 right-0"
squareSize={3}
Expand All @@ -37,4 +53,4 @@ const Header = ({title}: {title: string}) => {
)
}

export default Header
export default Header