Skip to content
Merged
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
10 changes: 9 additions & 1 deletion apps/web/src/app/(main)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";
import Sidebar from "@/components/dashboard/Sidebar";
import { AnimatePresence } from "framer-motion";
import FiltersContainer from "@/components/ui/FiltersContainer";
import { useFilterStore } from "@/store/useFilterStore";
import { useShowSidebar } from "@/store/useShowSidebar";
Expand All @@ -17,9 +18,16 @@ export default function DashboardLayout({
return (
<div className="flex w-screen h-screen bg-ox-content overflow-hidden">
{showFilters && <FiltersContainer />}
<aside className={`h-full ${!showSidebar && "hidden xl:block"}`}>
<aside className="hidden xl:block h-full">
<Sidebar />
</aside>
<AnimatePresence>
{showSidebar && (
<div className="xl:hidden h-full">
<Sidebar overlay />
</div>
)}
</AnimatePresence>
<div className="flex-1 flex flex-col h-full bg-ox-content">
<div className="xl:hidden flex items-center h-16 px-4 border-b border-ox-header bg-ox-content">
<IconWrapper onClick={() => setShowSidebar(true)}>
Expand Down
138 changes: 72 additions & 66 deletions apps/web/src/components/dashboard/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
import React, { useState } from "react";
import Link from "next/link";
import SidebarItem from "../sidebar/SidebarItem";
import { usePathname, useRouter } from "next/navigation";
import { useRouter } from "next/navigation";
import { IconWrapper } from "../ui/IconWrapper";
import { motion, AnimatePresence } from "framer-motion";
import {
XMarkIcon,
HomeIcon,
FolderIcon,
ArrowRightOnRectangleIcon,
ChevronDoubleLeftIcon,
ChevronDoubleRightIcon,
SparklesIcon,
StarIcon,
DocumentTextIcon,
Expand All @@ -22,6 +21,7 @@ import { signOut, useSession } from "next-auth/react";
import { ProfilePic } from "./ProfilePic";
import { useSubscription } from "@/hooks/useSubscription";
import { OpensoxProBadge } from "../sheet/OpensoxProBadge";
import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';

const SIDEBAR_ROUTES = [
{
Expand All @@ -41,15 +41,8 @@ const SIDEBAR_ROUTES = [
},
];

const getSidebarLinkClassName = (currentPath: string, routePath: string) => {
const isActive = currentPath === routePath;
return `${isActive ? "text-ox-purple" : "text-ox-white"}`;
};

export default function Sidebar() {
const { showSidebar, setShowSidebar, isCollapsed, toggleCollapsed } =
useShowSidebar();
const pathname = usePathname();
export default function Sidebar({ overlay = false }: { overlay?: boolean }) {
const { setShowSidebar, isCollapsed, toggleCollapsed } = useShowSidebar();
const router = useRouter();
const { isPaidUser } = useSubscription();

Expand All @@ -64,17 +57,22 @@ export default function Sidebar() {
router.push("/pricing");
}
};
const desktopWidth = isCollapsed ? 80 : 288;
const mobileWidth = desktopWidth;

return (
<div
className={`h-screen ${
isCollapsed ? "w-20" : "w-72"
} flex flex-col bg-ox-sidebar border-r border-ox-header z-50 transition-all duration-300 ease-out ${
showSidebar ? "fixed xl:relative left-0 top-0 bottom-0" : ""
<motion.div
className={`h-screen flex flex-col bg-ox-sidebar border-r border-ox-header 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}
transition={{ type: "spring", stiffness: 260, damping: 30 }}
style={{ width: overlay ? mobileWidth : desktopWidth }}
>
{/* Mobile header */}
<div className="flex justify-between px-4 py-4 border-b border-ox-header xl:hidden bg-ox-sidebar">
<div className="flex justify-between items-center h-16 px-4 border-b border-ox-header xl:hidden bg-ox-sideba">
<div className="flex items-center">
<Link
href="/"
Expand Down Expand Up @@ -103,18 +101,17 @@ export default function Sidebar() {
className={isCollapsed ? "w-full flex justify-center" : ""}
>
{isCollapsed ? (
<ChevronDoubleRightIcon className="size-5 text-ox-purple" />
<ChevronRightIcon className="size-5 text-ox-purple" />
) : (
<ChevronDoubleLeftIcon className="size-5 text-ox-purple" />
<ChevronLeftIcon className="size-5 text-ox-purple" />
)}
</IconWrapper>
</div>

<div className="sidebar-body flex-grow flex-col overflow-y-auto px-3 py-4 space-y-1">
<div className="sidebar-body flex-grow flex-col overflow-y-auto px-3 py-4">
{SIDEBAR_ROUTES.map((route) => {
const activeClass = getSidebarLinkClassName(pathname, route.path);
return (
<Link href={route.path} className={activeClass} key={route.path}>
<Link href={route.path} key={route.path}>
<SidebarItem
itemName={route.label}
icon={route.icon}
Expand All @@ -131,14 +128,14 @@ export default function Sidebar() {
/>
{!isCollapsed && !isPaidUser ? (
<div
className="w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 gap-3 pl-3 hover:bg-[#121214]"
className="w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 gap-3 pl-3 hover:bg-[#292929] group"
onClick={proClickHandler}
>
<span className="shrink-0 text-[#eaeaea]">
<span className="shrink-0 text-[#eaeaea] group-hover:text-white transition-colors">
<StarIcon className="size-5" />
</span>
<div className="flex items-center gap-1">
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-ox-purple">
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-white transition-colors">
Opensox Pro
</h1>
<OpensoxProBadge className="px-1.5 py-0.5 scale-75" />
Expand All @@ -156,7 +153,7 @@ export default function Sidebar() {

{/* Bottom profile */}
<ProfileMenu isCollapsed={isCollapsed} />
</div>
</motion.div>
);
}

Expand Down Expand Up @@ -189,7 +186,7 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) {
return (
<div className="px-3 py-4 border-t border-ox-header bg-ox-sidebar relative profile-menu-container">
<div
className={`group flex items-center rounded-md bg-ox-content border border-ox-header p-2 transition-all duration-300 ease-out cursor-pointer ${
className={`group flex items-center rounded-md bg-ox-profile-card border border-ox-header p-2 transition-all duration-300 ease-out cursor-pointer ${
isCollapsed ? "justify-center" : "gap-3"
}`}
onClick={() => setOpen((s) => !s)}
Expand All @@ -203,53 +200,62 @@ function ProfileMenu({ isCollapsed }: { isCollapsed: boolean }) {
</span>
<span className="text-[10px] text-zinc-400">{userEmail}</span>
</div>
<ChevronDoubleLeftIcon
<ChevronLeftIcon
className={`size-4 text-zinc-400 transition-transform ${open ? "rotate-90" : "-rotate-90"}`}
/>
</div>
)}
</div>
{/* Profile Card Dropdown */}
{!isCollapsed && open && (
<div className="absolute bottom-full left-3 right-3 mb-2 bg-ox-content border border-ox-header rounded-lg shadow-xl overflow-hidden z-50">
{/* User Info Section */}
<div className="p-3 border-b border-ox-header">
<div className="flex items-center gap-3">
<ProfilePic imageUrl={userImage} />
<div className="flex flex-col">
<span className="text-sm text-white font-semibold">
{fullName}
</span>
<span className="text-xs text-zinc-400">{userEmail}</span>
<AnimatePresence>
{!isCollapsed && open && (
<motion.div
key="profile-dropdown"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.18 }}
className="absolute bottom-full left-3 right-3 mb-2 bg-ox-profile-card border border-ox-header rounded-lg shadow-xl overflow-hidden z-50"
>
{/* User Info Section */}
<div className="p-3 border-b border-ox-header">
<div className="flex items-center gap-3">
<ProfilePic imageUrl={userImage} />
<div className="flex flex-col">
<span className="text-sm text-white font-semibold">
{fullName}
</span>
<span className="text-xs text-zinc-400">{userEmail}</span>
</div>
</div>
</div>
</div>

{/* Menu Items */}
<div className="py-1">
<button
onClick={() => {
router.push("/dashboard/account");
setOpen(false);
}}
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
>
<Cog6ToothIcon className="size-4" />
<span>Account Settings</span>
</button>
<button
onClick={() => {
signOut({ callbackUrl: "/" });
setOpen(false);
}}
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
>
<ArrowRightOnRectangleIcon className="size-4" />
<span>Logout</span>
</button>
</div>
</div>
)}
{/* Menu Items */}
<div className="py-1">
<button
onClick={() => {
router.push("/dashboard/account");
setOpen(false);
}}
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
>
<Cog6ToothIcon className="size-4" />
<span>Account Settings</span>
</button>
<button
onClick={() => {
signOut({ callbackUrl: "/" });
setOpen(false);
}}
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-[#eaeaea] hover:bg-ox-sidebar transition-colors"
>
<ArrowRightOnRectangleIcon className="size-4" />
<span>Logout</span>
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
10 changes: 7 additions & 3 deletions apps/web/src/components/sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ export default function SidebarItem({ itemName, onclick, icon, collapsed = false
<div
className={`w-full h-[44px] flex items-center rounded-md cursor-pointer transition-colors px-2 ${
collapsed ? "justify-center" : "gap-3 pl-3"
} hover:bg-[#121214]`}
} hover:bg-[#292929] group`}
onClick={onclick}
>
{icon && <span className="shrink-0 text-[#eaeaea]">{icon}</span>}
{icon && (
<span className="shrink-0 text-[#eaeaea] group-hover:text-white transition-colors">
{icon}
</span>
)}
{!collapsed && (
<div className="flex items-center gap-1.5">
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-ox-purple">
<h1 className="text-xs font-medium text-[#c8c8c8] group-hover:text-white transition-colors">
{itemName}
</h1>
{badge}
Expand Down
5 changes: 3 additions & 2 deletions apps/web/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ const config: Config = {
"ox-white": "text-slate-400",
"ox-black-1": "#0E0E10",
"ox-black-2": "#15161A",
"ox-sidebar": "#262626",
"ox-content": "#1E1E1E",
"ox-sidebar": "#141414",
"ox-profile-card": "#1E1E1E",
"ox-content": "#1A1A1A",
"ox-header": "#363636",
},
borderRadius: {
Expand Down