From e2e6fecf0ab4311751c93733ba9030b8930f8fd8 Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:14:12 +0300 Subject: [PATCH 1/4] fix flickering --- .../_components/AnimatedIcons/Refer.tsx | 1 + .../dashboard/_components/DashboardInner.tsx | 368 +----------------- .../dashboard/_components/Navbar/Desktop.tsx | 10 +- .../dashboard/_components/Navbar/Top.tsx | 365 +++++++++++++++++ apps/web/app/(org)/dashboard/layout.tsx | 6 +- apps/web/app/globals.css | 30 +- 6 files changed, 412 insertions(+), 368 deletions(-) create mode 100644 apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx diff --git a/apps/web/app/(org)/dashboard/_components/AnimatedIcons/Refer.tsx b/apps/web/app/(org)/dashboard/_components/AnimatedIcons/Refer.tsx index a7572753c7..aae32a2be9 100644 --- a/apps/web/app/(org)/dashboard/_components/AnimatedIcons/Refer.tsx +++ b/apps/web/app/(org)/dashboard/_components/AnimatedIcons/Refer.tsx @@ -53,6 +53,7 @@ const ReferIcon = forwardRef( onAnimationComplete={() => setIsAnimating(false)} {...props} > + Box = { - "/dashboard/caps": "Caps", - "/dashboard/shared-caps": "Shared Caps", - "/dashboard/settings/organization": "Organization Settings", - "/dashboard/settings/account": "Account Settings", - "/dashboard/spaces": "Spaces", - "/dashboard/spaces/browse": "Browse Spaces", - }; - - const title = activeSpace ? activeSpace.name : titles[pathname] || ""; - const { theme, setThemeHandler } = useTheme(); - const [toggleNotifications, setToggleNotifications] = useState(false); - const bellRef = useRef(null); - const notificationsRef: MutableRefObject = useClickAway( - (e) => { - if (bellRef.current && !bellRef.current.contains(e.target as Node)) { - setToggleNotifications(false); - } - }, - ); - const isSharedCapsPage = pathname === "/dashboard/shared-caps"; return (
- {/* NavBar */} -
-
- {activeSpace && Space} -
- {activeSpace && - (activeSpace.iconUrl ? ( - {activeSpace?.name - ) : ( - - ))} -

- {title} -

-
-
-
- {buildEnv.NEXT_PUBLIC_IS_CAP && } -
{ - setToggleNotifications(!toggleNotifications); - }} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - setToggleNotifications(!toggleNotifications); - } - }} - tabIndex={0} - role="button" - aria-label={`Notifications${ - anyNewNotifications ? " (new notifications available)" : "" - }`} - aria-expanded={toggleNotifications} - className="hidden relative justify-center data-[state=open]:hover:bg-gray-5 items-center bg-gray-3 - rounded-full transition-colors cursor-pointer lg:flex - hover:bg-gray-5 data-[state=open]:bg-gray-5 - focus:outline-none - size-9" - > - {anyNewNotifications && ( -
-
-
-
-
-
- )} - - - {toggleNotifications && } - -
-
{ - if (document.startViewTransition) { - document.startViewTransition(() => { - setThemeHandler(theme === "light" ? "dark" : "light"); - }); - } else { - setThemeHandler(theme === "light" ? "dark" : "light"); - } - }} - className="hidden justify-center items-center rounded-full transition-colors cursor-pointer bg-gray-3 lg:flex hover:bg-gray-5 size-9" - > - -
- -
-
- {/*End of NavBar*/} +
-
{children}
+ {/* Content Area - this div prevents flickering when the sidebar is toggled */} +
+
{children}
+
{isSharedCapsPage && activeOrganization?.members && ( ); } - -const User = () => { - const [menuOpen, setMenuOpen] = useState(false); - const [upgradeModalOpen, setUpgradeModalOpen] = useState(false); - const { user, isSubscribed } = useDashboardContext(); - - const menuItems = useMemo( - () => [ - { - name: "Homepage", - icon: , - href: "/home", - onClick: () => setMenuOpen(false), - iconClassName: "text-gray-11 group-hover:text-gray-12", - showCondition: true, - }, - { - name: "Upgrade to Pro", - icon: , - onClick: () => { - setMenuOpen(false); - setUpgradeModalOpen(true); - }, - iconClassName: "text-amber-400 group-hover:text-amber-500", - showCondition: !isSubscribed && buildEnv.NEXT_PUBLIC_IS_CAP, - }, - { - name: "Earn 40% Referral", - icon: , - href: "/dashboard/refer", - onClick: () => setMenuOpen(false), - iconClassName: "text-gray-11 group-hover:text-gray-12", - showCondition: buildEnv.NEXT_PUBLIC_IS_CAP, - }, - { - name: "Settings", - icon: , - href: "/dashboard/settings/account", - onClick: () => setMenuOpen(false), - iconClassName: "text-gray-11 group-hover:text-gray-12", - showCondition: true, - }, - { - name: "Chat Support", - icon: , - onClick: () => window.open("https://cap.link/discord", "_blank"), - iconClassName: "text-gray-11 group-hover:text-gray-12", - showCondition: true, - }, - { - name: "Download App", - icon: , - onClick: () => window.open("https://cap.so/download", "_blank"), - iconClassName: "text-gray-11 group-hover:text-gray-12", - showCondition: true, - }, - { - name: "Sign Out", - icon: , - onClick: () => signOut(), - iconClassName: "text-gray-11 group-hover:text-gray-12", - showCondition: true, - }, - ], - [], - ); - - return ( - <> - - - -
-
- {user.image ? ( - {user.name - ) : ( - - )} - - {user.name ?? "User"} - -
- -
-
- - - - {menuItems - .filter((item) => item.showCondition) - .map((item, index) => ( - - ))} - - - -
- - ); -}; - -interface Props { - icon: React.ReactElement; - name: string; - href?: string; - onClick: () => void; - iconClassName?: string; -} - -const MenuItem = memo(({ icon, name, href, onClick, iconClassName }: Props) => { - const iconRef = useRef(null); - return ( - { - iconRef.current?.startAnimation(); - }} - onMouseLeave={() => { - iconRef.current?.stopAnimation(); - }} - > - -
- {cloneElement(icon, { - ref: iconRef, - className: iconClassName, - size: 14, - })} -
-

{name}

- -
- ); -}); - -const ReferButton = () => { - const iconRef = useRef(null); - - return ( - - {/* Red notification dot with pulse animation */} -
-
-
-
-
-
- -
{ - iconRef.current?.startAnimation(); - }} - onMouseLeave={() => { - iconRef.current?.stopAnimation(); - }} - className="flex justify-center items-center rounded-full transition-colors cursor-pointer bg-gray-3 hover:bg-gray-5 size-9" - > - {cloneElement(, { - ref: iconRef, - className: "text-gray-12 size-3.5", - })} -
- - ); -}; diff --git a/apps/web/app/(org)/dashboard/_components/Navbar/Desktop.tsx b/apps/web/app/(org)/dashboard/_components/Navbar/Desktop.tsx index 00daa6b14f..e683f4dd33 100644 --- a/apps/web/app/(org)/dashboard/_components/Navbar/Desktop.tsx +++ b/apps/web/app/(org)/dashboard/_components/Navbar/Desktop.tsx @@ -31,10 +31,10 @@ export const DesktopNav = () => { }, [toggleSidebarCollapsed]); return ( - { }, }} className={clsx( - "hidden relative z-50 h-full will-change-[width] lg:flex group bg-gray-1", + "hidden relative z-50 flex-1 h-full [grid-area:sidebar] will-change-[width] lg:flex group bg-gray-1", )} > -
+
{
- + ); }; diff --git a/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx b/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx new file mode 100644 index 0000000000..1a4567abf7 --- /dev/null +++ b/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx @@ -0,0 +1,365 @@ +"use client"; + +import { buildEnv } from "@cap/env"; +import { + Avatar, + Command, + CommandGroup, + CommandItem, + Popover, + PopoverContent, + PopoverTrigger, +} from "@cap/ui"; +import { faBell, faMoon, faSun } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useClickAway } from "@uidotdev/usehooks"; +import clsx from "clsx"; +import { AnimatePresence } from "framer-motion"; +import { MoreVertical } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { signOut } from "next-auth/react"; +import { + cloneElement, + type MutableRefObject, + memo, + useMemo, + useRef, + useState, +} from "react"; +import Notifications from "@/app/(org)/dashboard/_components/Notifications"; +import { UpgradeModal } from "@/components/UpgradeModal"; +import { useDashboardContext, useTheme } from "../../Contexts"; +import { + ArrowUpIcon, + DownloadIcon, + HomeIcon, + LogoutIcon, + MessageCircleMoreIcon, + ReferIcon, + SettingsGearIcon, +} from "../AnimatedIcons"; +import { type DownloadIconHandle } from "../AnimatedIcons/Download"; +import { type ReferIconHandle } from "../AnimatedIcons/Refer"; + +const Top = () => { + const { activeSpace, anyNewNotifications } = useDashboardContext(); + const [toggleNotifications, setToggleNotifications] = useState(false); + const bellRef = useRef(null); + const { theme, setThemeHandler } = useTheme(); + + const pathname = usePathname(); + + const titles: Record = { + "/dashboard/caps": "Caps", + "/dashboard/folder": "Caps", + "/dashboard/shared-caps": "Shared Caps", + "/dashboard/settings/organization": "Organization Settings", + "/dashboard/settings/account": "Account Settings", + "/dashboard/spaces": "Spaces", + "/dashboard/spaces/browse": "Browse Spaces", + }; + + const title = activeSpace ? activeSpace.name : pathname.includes("/dashboard/folder") ? "Caps" : titles[pathname] || ""; + + const notificationsRef: MutableRefObject = useClickAway( + (e) => { + if (bellRef.current && !bellRef.current.contains(e.target as Node)) { + setToggleNotifications(false); + } + }, + ); + + return ( +
+
+ {activeSpace && Space} +
+ {activeSpace && + (activeSpace.iconUrl ? ( + {activeSpace?.name + ) : ( + + ))} +

+ {title} +

+
+
+
+ {buildEnv.NEXT_PUBLIC_IS_CAP && } +
{ + setToggleNotifications(!toggleNotifications); + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + setToggleNotifications(!toggleNotifications); + } + }} + tabIndex={0} + role="button" + aria-label={`Notifications${ + anyNewNotifications ? " (new notifications available)" : "" + }`} + aria-expanded={toggleNotifications} + className="hidden relative justify-center data-[state=open]:hover:bg-gray-5 items-center bg-gray-3 + rounded-full transition-colors cursor-pointer lg:flex + hover:bg-gray-5 data-[state=open]:bg-gray-5 + focus:outline-none + size-9" + > + {anyNewNotifications && ( +
+
+
+
+
+
+ )} + + + {toggleNotifications && } + +
+
{ + if (document.startViewTransition) { + document.startViewTransition(() => { + setThemeHandler(theme === "light" ? "dark" : "light"); + }); + } else { + setThemeHandler(theme === "light" ? "dark" : "light"); + } + }} + className="hidden justify-center items-center rounded-full transition-colors cursor-pointer bg-gray-3 lg:flex hover:bg-gray-5 size-9" + > + +
+ +
+
+ ); +}; + +const User = () => { + const [menuOpen, setMenuOpen] = useState(false); + const [upgradeModalOpen, setUpgradeModalOpen] = useState(false); + const { user, isSubscribed } = useDashboardContext(); + + const menuItems = useMemo( + () => [ + { + name: "Homepage", + icon: , + href: "/home", + onClick: () => setMenuOpen(false), + iconClassName: "text-gray-11 group-hover:text-gray-12", + showCondition: true, + }, + { + name: "Upgrade to Pro", + icon: , + onClick: () => { + setMenuOpen(false); + setUpgradeModalOpen(true); + }, + iconClassName: "text-amber-400 group-hover:text-amber-500", + showCondition: !isSubscribed && buildEnv.NEXT_PUBLIC_IS_CAP, + }, + { + name: "Earn 40% Referral", + icon: , + href: "/dashboard/refer", + onClick: () => setMenuOpen(false), + iconClassName: "text-gray-11 group-hover:text-gray-12", + showCondition: buildEnv.NEXT_PUBLIC_IS_CAP, + }, + { + name: "Settings", + icon: , + href: "/dashboard/settings/account", + onClick: () => setMenuOpen(false), + iconClassName: "text-gray-11 group-hover:text-gray-12", + showCondition: true, + }, + { + name: "Chat Support", + icon: , + onClick: () => window.open("https://cap.link/discord", "_blank"), + iconClassName: "text-gray-11 group-hover:text-gray-12", + showCondition: true, + }, + { + name: "Download App", + icon: , + onClick: () => window.open("https://cap.so/download", "_blank"), + iconClassName: "text-gray-11 group-hover:text-gray-12", + showCondition: true, + }, + { + name: "Sign Out", + icon: , + onClick: () => signOut(), + iconClassName: "text-gray-11 group-hover:text-gray-12", + showCondition: true, + }, + ], + [], + ); + + return ( + <> + + + +
+
+ {user.image ? ( + {user.name + ) : ( + + )} + + {user.name ?? "User"} + +
+ +
+
+ + + + {menuItems + .filter((item) => item.showCondition) + .map((item, index) => ( + + ))} + + + +
+ + ); +}; + +interface Props { + icon: React.ReactElement; + name: string; + href?: string; + onClick: () => void; + iconClassName?: string; +} + +const MenuItem = memo(({ icon, name, href, onClick, iconClassName }: Props) => { + const iconRef = useRef(null); + return ( + { + iconRef.current?.startAnimation(); + }} + onMouseLeave={() => { + iconRef.current?.stopAnimation(); + }} + > + +
+ {cloneElement(icon, { + ref: iconRef, + className: iconClassName, + size: 14, + })} +
+

{name}

+ +
+ ); +}); + +const ReferButton = () => { + const iconRef = useRef(null); + + return ( + + {/* Red notification dot with pulse animation */} +
+
+
+
+
+
+ +
{ + iconRef.current?.startAnimation(); + }} + onMouseLeave={() => { + iconRef.current?.stopAnimation(); + }} + className="flex justify-center items-center rounded-full transition-colors cursor-pointer bg-gray-3 hover:bg-gray-5 size-9" + > + {cloneElement(, { + ref: iconRef, + className: "text-gray-12 size-3.5", + })} +
+ + ); +}; + +export default Top; diff --git a/apps/web/app/(org)/dashboard/layout.tsx b/apps/web/app/(org)/dashboard/layout.tsx index 6d5abb32db..4c9895665c 100644 --- a/apps/web/app/(org)/dashboard/layout.tsx +++ b/apps/web/app/(org)/dashboard/layout.tsx @@ -79,11 +79,9 @@ export default async function DashboardLayout({ anyNewNotifications={anyNewNotifications} userPreferences={userPreferences} > -
- -
+
{children}
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index c33360d422..b98b720968 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -52,6 +52,18 @@ html { background-color: #f2f2f2; } + +.dashboard-grid { + display: grid; + grid-template-columns: auto 1fr 1fr; + grid-template-areas: + "sidebar main main" + "sidebar main main"; + height: 100dvh; + width: 100vw; + min-height: 100dvh; +} + .left-perspective { transform: scale(1.2) rotateX(47deg) rotateY(31deg) rotate(324deg); } @@ -66,9 +78,14 @@ html { border-radius: 10px; } -/* Handle */ ::-webkit-scrollbar-thumb { - background: rgb(116, 116, 116); + background: rgb(178, 178, 178); + border-radius: 10px; +} + +/* Handle */ +.dark ::-webkit-scrollbar-thumb { + background: rgb(61, 61, 61); border-radius: 10px; } @@ -291,6 +308,15 @@ a.bg-primary:hover { mask-composite: exclude; } +@media all and (max-width: 1024px) { + .dashboard-grid { + grid-template-columns: 1fr; + grid-template-areas: + "main" + "main"; + } +} + @media (prefers-color-scheme: dark) { body, html { From b0a5f4ec30c6ae47277805f28177d6b96b33180b Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:45:01 +0300 Subject: [PATCH 2/4] workaround fixing flicker --- .../dashboard/_components/DashboardInner.tsx | 11 ++++++++--- .../(org)/dashboard/caps/components/Folder.tsx | 17 ++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/web/app/(org)/dashboard/_components/DashboardInner.tsx b/apps/web/app/(org)/dashboard/_components/DashboardInner.tsx index 3ec081cd07..baed0f2193 100644 --- a/apps/web/app/(org)/dashboard/_components/DashboardInner.tsx +++ b/apps/web/app/(org)/dashboard/_components/DashboardInner.tsx @@ -20,11 +20,16 @@ export default function DashboardInner({
- {/* Content Area - this div prevents flickering when the sidebar is toggled */} -
+ {/* Top cap: renders rounded corner and top/side borders without affecting scroller */} +
+ {/* Scrolling content area shares border/background; top border removed to meet cap */} +
{children}
diff --git a/apps/web/app/(org)/dashboard/caps/components/Folder.tsx b/apps/web/app/(org)/dashboard/caps/components/Folder.tsx index 2cdf34c3db..b70e62fcfb 100644 --- a/apps/web/app/(org)/dashboard/caps/components/Folder.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/Folder.tsx @@ -319,7 +319,7 @@ const FolderCard = ({ onDragLeave={handleDragLeave} onDrop={handleDrop} className={clsx( - "flex justify-between items-center px-4 py-4 w-full h-auto rounded-lg border transition-colors duration-200 cursor-pointer bg-gray-3 hover:bg-gray-4 hover:border-gray-6", + "flex justify-between items-center px-4 py-4 w-full h-auto rounded-lg border transition-all duration-200 cursor-pointer bg-gray-3 hover:bg-gray-4 hover:border-gray-6", isDragOver ? "border-blue-10 bg-gray-4" : "border-gray-5", isMovingVideo && "opacity-70", )} @@ -331,6 +331,7 @@ const FolderCard = ({ />
{ + e.preventDefault(); e.stopPropagation(); }} className="flex flex-col justify-center h-10" @@ -347,11 +348,11 @@ const FolderCard = ({ await updateFolderNameHandler(); } }} - onKeyDown={(e) => { + onKeyDown={async (e) => { if (e.key === "Enter") { setIsRenaming(false); if (updateName.trim() !== name) { - updateFolderNameHandler(); + await updateFolderNameHandler(); } } }} @@ -359,15 +360,17 @@ const FolderCard = ({ focus:ring-0 focus:border-none text-gray-12 text-[15px] max-w-[116px] truncate p-0 m-0 h-[22px] leading-[22px] overflow-hidden font-normal tracking-normal" /> ) : ( -

{ + e.preventDefault(); e.stopPropagation(); setIsRenaming(true); }} - className="text-[15px] truncate text-gray-12 w-full max-w-[116px] m-0 p-0 h-[22px] leading-[22px] font-normal tracking-normal" > - {updateName} -

+

+ {updateName} +

+
)}

{`${videoCount} ${ videoCount === 1 ? "video" : "videos" From 37b90cd2ea731e1b8be79b40e0fa07bba3c56e7e Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:52:22 +0300 Subject: [PATCH 3/4] use gray radix variant for scrollbar color --- apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx | 6 +++++- apps/web/app/(org)/dashboard/layout.tsx | 2 +- apps/web/app/globals.css | 8 +------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx b/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx index 1a4567abf7..6944e1bcab 100644 --- a/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx +++ b/apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx @@ -61,7 +61,11 @@ const Top = () => { "/dashboard/spaces/browse": "Browse Spaces", }; - const title = activeSpace ? activeSpace.name : pathname.includes("/dashboard/folder") ? "Caps" : titles[pathname] || ""; + const title = activeSpace + ? activeSpace.name + : pathname.includes("/dashboard/folder") + ? "Caps" + : titles[pathname] || ""; const notificationsRef: MutableRefObject = useClickAway( (e) => { diff --git a/apps/web/app/(org)/dashboard/layout.tsx b/apps/web/app/(org)/dashboard/layout.tsx index 4c9895665c..3a4f21038a 100644 --- a/apps/web/app/(org)/dashboard/layout.tsx +++ b/apps/web/app/(org)/dashboard/layout.tsx @@ -80,7 +80,7 @@ export default async function DashboardLayout({ userPreferences={userPreferences} >

- +
{children} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index b98b720968..36d0e193bd 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -79,13 +79,7 @@ html { } ::-webkit-scrollbar-thumb { - background: rgb(178, 178, 178); - border-radius: 10px; -} - -/* Handle */ -.dark ::-webkit-scrollbar-thumb { - background: rgb(61, 61, 61); + background: var(--gray-7); border-radius: 10px; } From 0ac2c3e1553bf9800541781fcfbe34f4e14d1eab Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:53:58 +0300 Subject: [PATCH 4/4] biome --- apps/web/app/globals.css | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 36d0e193bd..2dfb1d7abc 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -52,7 +52,6 @@ html { background-color: #f2f2f2; } - .dashboard-grid { display: grid; grid-template-columns: auto 1fr 1fr;