diff --git a/frontend/package.json b/frontend/package.json
index c7ec783..9bca7a5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,9 +13,11 @@
"@hookform/resolvers": "^3.3.0",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-scroll-area": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-tooltip": "^1.0.6",
+ "@tabler/icons-react": "^2.32.0",
"@tanstack/react-query": "^4.33.0",
"@types/node": "20.4.8",
"@types/react": "18.2.18",
diff --git a/frontend/src/app/(dashboard)/shell.tsx b/frontend/src/app/(dashboard)/shell.tsx
index e661e99..84eb57e 100644
--- a/frontend/src/app/(dashboard)/shell.tsx
+++ b/frontend/src/app/(dashboard)/shell.tsx
@@ -1,18 +1,23 @@
-"use client"
+'use client';
-export default function Shell({ children }: {
- children: React.ReactNode
-}) {
+import Sidebar from '@/components/sidebar/sidebar';
+import TopBar from '@/components/sidebar/topbar';
+export default function Shell({ children }: { children: React.ReactNode }) {
return (
);
}
diff --git a/frontend/src/components/sidebar/actions/sidebar-actions.tsx b/frontend/src/components/sidebar/actions/sidebar-actions.tsx
new file mode 100644
index 0000000..8bffc71
--- /dev/null
+++ b/frontend/src/components/sidebar/actions/sidebar-actions.tsx
@@ -0,0 +1,37 @@
+import { ReactNode } from 'react';
+import {
+ IconHome,
+ IconSearch,
+ IconSettings,
+ IconFilePlus,
+} from '@tabler/icons-react';
+
+export type NavigationMenuType = {
+ label: string;
+ path: string;
+ icon: ReactNode;
+ isActive?: boolean;
+ onClick?: React.MouseEventHandler;
+};
+export const navigationMenu: NavigationMenuType[] = [
+ {
+ label: 'Home',
+ path: '',
+ icon: ,
+ },
+ {
+ label: 'Search',
+ path: '',
+ icon: ,
+ },
+ {
+ label: 'Settings',
+ path: '',
+ icon: ,
+ },
+ {
+ label: 'New Page',
+ path: '',
+ icon: ,
+ },
+];
diff --git a/frontend/src/components/sidebar/atoms/sidebar-atom.ts b/frontend/src/components/sidebar/atoms/sidebar-atom.ts
new file mode 100644
index 0000000..a5d6f5d
--- /dev/null
+++ b/frontend/src/components/sidebar/atoms/sidebar-atom.ts
@@ -0,0 +1,5 @@
+import { atomWithWebStorage } from "@/lib/jotai-helper";
+import { atom } from "jotai";
+
+export const desktopSidebarAtom = atomWithWebStorage('showSidebar',true);
+export const mobileSidebarAtom = atom(false);
diff --git a/frontend/src/components/sidebar/hooks/use-toggle-sidebar.ts b/frontend/src/components/sidebar/hooks/use-toggle-sidebar.ts
new file mode 100644
index 0000000..5dbe3ba
--- /dev/null
+++ b/frontend/src/components/sidebar/hooks/use-toggle-sidebar.ts
@@ -0,0 +1,8 @@
+import { useAtom } from "jotai";
+
+export function useToggleSidebar(sidebarAtom) {
+ const [sidebarState, setSidebarState] = useAtom(sidebarAtom);
+ return () => {
+ setSidebarState(!sidebarState);
+ }
+}
diff --git a/frontend/src/components/sidebar/sidebar-section.tsx b/frontend/src/components/sidebar/sidebar-section.tsx
new file mode 100644
index 0000000..3c1cb4d
--- /dev/null
+++ b/frontend/src/components/sidebar/sidebar-section.tsx
@@ -0,0 +1,15 @@
+import React, { ReactNode } from "react";
+import { cn } from "@/lib/utils";
+
+interface SidebarSectionProps {
+ className?: string;
+ children: ReactNode
+}
+
+export function SidebarSection({className, children}: SidebarSectionProps) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/frontend/src/components/sidebar/sidebar.tsx b/frontend/src/components/sidebar/sidebar.tsx
new file mode 100644
index 0000000..d67713a
--- /dev/null
+++ b/frontend/src/components/sidebar/sidebar.tsx
@@ -0,0 +1,62 @@
+import { useIsMobile } from '@/hooks/use-is-mobile';
+import { useAtom } from 'jotai';
+import {
+ desktopSidebarAtom,
+ mobileSidebarAtom,
+} from '@/components/sidebar/atoms/sidebar-atom';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { IconFileText } from '@tabler/icons-react';
+import { SidebarSection } from '@/components/sidebar/sidebar-section';
+import {
+ navigationMenu,
+ NavigationMenuType,
+} from '@/components/sidebar/actions/sidebar-actions';
+import ButtonWithIcon from '@/components/ui/button-with-icon';
+
+export default function Sidebar() {
+ const isMobile = useIsMobile();
+ const [isSidebarOpen] = useAtom(
+ isMobile ? mobileSidebarAtom : desktopSidebarAtom
+ );
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/sidebar/topbar.tsx b/frontend/src/components/sidebar/topbar.tsx
new file mode 100644
index 0000000..a76ad75
--- /dev/null
+++ b/frontend/src/components/sidebar/topbar.tsx
@@ -0,0 +1,38 @@
+'use client';
+
+import { ReactNode } from 'react';
+
+import { useIsMobile } from '@/hooks/use-is-mobile';
+import {
+ desktopSidebarAtom,
+ mobileSidebarAtom,
+} from '@/components/sidebar/atoms/sidebar-atom';
+import { useToggleSidebar } from './hooks/use-toggle-sidebar';
+import ButtonWithIcon from '../ui/button-with-icon';
+import { IconLayoutSidebarLeftCollapse } from '@tabler/icons-react';
+
+export default function TopBar() {
+ const isMobile = useIsMobile();
+ const sidebarStateAtom = isMobile ? mobileSidebarAtom : desktopSidebarAtom;
+
+ const toggleSidebar = useToggleSidebar(sidebarStateAtom);
+
+ return (
+
+
+
+
+ }
+ variant={'ghost'}
+ onClick={toggleSidebar}
+ >
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/ui/button-with-icon.tsx b/frontend/src/components/ui/button-with-icon.tsx
new file mode 100644
index 0000000..2069f21
--- /dev/null
+++ b/frontend/src/components/ui/button-with-icon.tsx
@@ -0,0 +1,18 @@
+import { ReactNode } from 'react';
+import { Button } from '@/components/ui/button';
+
+interface ButtonIconProps {
+ icon: ReactNode;
+ children?: ReactNode;
+}
+
+type Props = ButtonIconProps & React.ComponentPropsWithoutRef;
+
+export default function ButtonWithIcon({ icon, children, ...rest }: Props) {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
index 4ecf369..b45cd50 100644
--- a/frontend/src/components/ui/button.tsx
+++ b/frontend/src/components/ui/button.tsx
@@ -24,7 +24,7 @@ const buttonVariants = cva(
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
- icon: "h-9 w-9",
+ icon: "h-7 w-7",
},
},
defaultVariants: {
diff --git a/frontend/src/components/ui/scroll-area.tsx b/frontend/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..54b87cd
--- /dev/null
+++ b/frontend/src/components/ui/scroll-area.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/frontend/src/features/auth/hooks/use-auth.ts b/frontend/src/features/auth/hooks/use-auth.ts
index f092fc5..d192825 100644
--- a/frontend/src/features/auth/hooks/use-auth.ts
+++ b/frontend/src/features/auth/hooks/use-auth.ts
@@ -6,7 +6,6 @@ import { useAtom } from "jotai";
import { authTokensAtom } from "@/features/auth/atoms/auth-tokens-atom";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
import { ILogin, IRegister } from "@/features/auth/types/auth.types";
-import { RESET } from "jotai/vanilla/utils/constants";
export default function useAuth() {
const [isLoading, setIsLoading] = useState(false);
@@ -21,7 +20,7 @@ export default function useAuth() {
try {
const res = await login(data);
setIsLoading(false);
- await setAuthToken(res.tokens);
+ setAuthToken(res.tokens);
router.push("/home");
} catch (err) {
@@ -40,7 +39,7 @@ export default function useAuth() {
const res = await register(data);
setIsLoading(false);
- await setAuthToken(res.tokens);
+ setAuthToken(res.tokens);
router.push("/home");
} catch (err) {
@@ -57,7 +56,7 @@ export default function useAuth() {
};
const handleLogout = async () => {
- await setAuthToken(RESET);
+ setAuthToken(null);
setCurrentUser('');
}
diff --git a/frontend/src/hooks/use-is-mobile.ts b/frontend/src/hooks/use-is-mobile.ts
new file mode 100644
index 0000000..d07f36d
--- /dev/null
+++ b/frontend/src/hooks/use-is-mobile.ts
@@ -0,0 +1,5 @@
+import { useMediaQuery } from '@/hooks/use-media-query';
+
+export function useIsMobile(): boolean {
+ return useMediaQuery(`(max-width: 768px)`);
+}
diff --git a/frontend/src/hooks/use-media-query.ts b/frontend/src/hooks/use-media-query.ts
new file mode 100644
index 0000000..316c5d4
--- /dev/null
+++ b/frontend/src/hooks/use-media-query.ts
@@ -0,0 +1,22 @@
+import { useEffect, useState } from 'react';
+
+export function useMediaQuery(query: string): boolean {
+ const [matches, setMatches] = useState(false);
+
+ useEffect(() => {
+ const media = window.matchMedia(query);
+ if (media.matches !== matches) {
+ setMatches(media.matches);
+ }
+
+ const listener = () => {
+ setMatches(media.matches);
+ };
+
+ media.addEventListener('change', listener);
+
+ return () => media.removeEventListener('change', listener);
+ }, [matches, query]);
+
+ return matches;
+}