diff --git a/apps/client/components.json b/apps/client/components.json
index fc9f93ae..b6232ec4 100644
--- a/apps/client/components.json
+++ b/apps/client/components.json
@@ -11,11 +11,10 @@
},
"iconLibrary": "lucide",
"aliases": {
- "components": "@/common/components",
- "utils": "@/libs/utils",
- "ui": "@/libs/ui",
- "lib": "@/libs",
- "hooks": "@/common/hooks"
+ "ui": "@/libs/ui/components",
+ "components": "@/libs/ui/components",
+ "hooks": "@/libs/ui/hooks",
+ "utils": "@/libs/ui/utils.ts"
},
"registries": {},
"$schema": "https://ui.shadcn.com/schema.json"
diff --git a/apps/client/package-lock.json b/apps/client/package-lock.json
index fcfa52a1..d07aecbd 100644
--- a/apps/client/package-lock.json
+++ b/apps/client/package-lock.json
@@ -10,8 +10,13 @@
"dependencies": {
"@clerk/clerk-react": "^5.35.3",
"@clerk/types": "^4.90.0",
+ "@radix-ui/react-avatar": "^1.1.10",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-form": "^1.15.0",
"@tanstack/react-query": "^5.90.2",
diff --git a/apps/client/package.json b/apps/client/package.json
index f56068a4..071852cd 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -17,8 +17,13 @@
"dependencies": {
"@clerk/clerk-react": "^5.35.3",
"@clerk/types": "^4.90.0",
+ "@radix-ui/react-avatar": "^1.1.10",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-form": "^1.15.0",
"@tanstack/react-query": "^5.90.2",
diff --git a/apps/client/src/app/app.tsx b/apps/client/src/app/app.tsx
index b1aa7aa5..a8a815c6 100644
--- a/apps/client/src/app/app.tsx
+++ b/apps/client/src/app/app.tsx
@@ -16,6 +16,8 @@ export function App() {
)
}
+// TODO: refactor loading to better handle changing auth states --> multiple loading screens encountered during login/logout
+
function EntryPoint() {
const [isReady, setIsReady] = useState(false)
const auth = useAuth()
diff --git a/apps/client/src/app/loading.tsx b/apps/client/src/app/loading.tsx
index c557951d..804f8eb1 100644
--- a/apps/client/src/app/loading.tsx
+++ b/apps/client/src/app/loading.tsx
@@ -3,8 +3,8 @@ import { useEffect, useState } from "react"
import { AnimatePresence, motion } from "motion/react"
import { EchoLogo } from "@/common/components/logos/echo-logo"
-import { MotionContainer } from "@/libs/ui/container"
-import { Page } from "@/libs/ui/page"
+import { MotionContainer } from "@/libs/ui/components/container"
+import { Page } from "@/libs/ui/components/page"
interface Props {
isReady: boolean
diff --git a/apps/client/src/common/components/sidebar/sidebar-footer.tsx b/apps/client/src/common/components/sidebar/sidebar-footer.tsx
new file mode 100644
index 00000000..9a7a2b1c
--- /dev/null
+++ b/apps/client/src/common/components/sidebar/sidebar-footer.tsx
@@ -0,0 +1,32 @@
+import { useUser } from "@clerk/clerk-react"
+import { ChevronsUpDown, UserRound } from "lucide-react"
+
+import { Avatar, AvatarFallback, AvatarImage } from "@/libs/ui/components/avatar"
+import { SidebarMenuButton } from "@/libs/ui/components/sidebar"
+
+export function Footer() {
+ const { user } = useUser()
+
+ return (
+
+
+
+
+
+
+
+
+ {user?.username}
+ {user?.primaryEmailAddress?.emailAddress}
+
+
+
+ )
+}
diff --git a/apps/client/src/common/components/sidebar/sidebar-header.tsx b/apps/client/src/common/components/sidebar/sidebar-header.tsx
new file mode 100644
index 00000000..ee6a7fb1
--- /dev/null
+++ b/apps/client/src/common/components/sidebar/sidebar-header.tsx
@@ -0,0 +1,16 @@
+import { Separator } from "@/libs/ui/components/separator"
+import { SidebarTrigger } from "@/libs/ui/components/sidebar"
+import Large from "@/libs/ui/components/typography/large"
+
+export function Header() {
+ return (
+
+
+
+ Navigation
+
+ )
+}
diff --git a/apps/client/src/common/components/sidebar/sidebar-nav.config.ts b/apps/client/src/common/components/sidebar/sidebar-nav.config.ts
new file mode 100644
index 00000000..f736f190
--- /dev/null
+++ b/apps/client/src/common/components/sidebar/sidebar-nav.config.ts
@@ -0,0 +1,27 @@
+import { linkOptions } from "@tanstack/react-router"
+import { House, MessageSquare, Search, UserRound } from "lucide-react"
+
+const options = linkOptions([
+ {
+ icon: House,
+ label: "Home",
+ to: "/home"
+ },
+ {
+ icon: Search,
+ label: "Search",
+ to: "/onboarding" // TODO: temporary avoid type error
+ },
+ {
+ icon: UserRound,
+ label: "Profile",
+ to: "/onboarding" // TODO: temporary avoid type error
+ },
+ {
+ icon: MessageSquare,
+ label: "Chat",
+ to: "/onboarding" // TODO: temporary avoid type error
+ }
+])
+
+export { options }
diff --git a/apps/client/src/common/components/sidebar/sidebar-nav.tsx b/apps/client/src/common/components/sidebar/sidebar-nav.tsx
new file mode 100644
index 00000000..6373007c
--- /dev/null
+++ b/apps/client/src/common/components/sidebar/sidebar-nav.tsx
@@ -0,0 +1,38 @@
+import { Link } from "@tanstack/react-router"
+
+import {
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem
+} from "@/libs/ui/components/sidebar"
+
+import { options } from "./sidebar-nav.config"
+
+export function Navigation() {
+ return (
+
+
+
+ {options.map((option) => (
+
+
+
+
+ {option.label}
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/client/src/common/components/sidebar/sidebar.tsx b/apps/client/src/common/components/sidebar/sidebar.tsx
new file mode 100644
index 00000000..7ad7b620
--- /dev/null
+++ b/apps/client/src/common/components/sidebar/sidebar.tsx
@@ -0,0 +1,24 @@
+import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader } from "@/libs/ui/components/sidebar"
+
+import { Footer } from "./sidebar-footer"
+import { Header } from "./sidebar-header"
+import { Navigation } from "./sidebar-nav"
+
+export function AppSidebar() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/client/src/features/auth/components/auth-form-wrapper.tsx b/apps/client/src/features/auth/components/auth-form-wrapper.tsx
index 0bff344c..e85f72de 100644
--- a/apps/client/src/features/auth/components/auth-form-wrapper.tsx
+++ b/apps/client/src/features/auth/components/auth-form-wrapper.tsx
@@ -1,6 +1,6 @@
import type React from "react"
-import { MotionContainer } from "@/libs/ui/container"
+import { MotionContainer } from "@/libs/ui/components/container"
import { BackToHome } from "./back-to-home"
diff --git a/apps/client/src/features/auth/components/back-to-home.tsx b/apps/client/src/features/auth/components/back-to-home.tsx
index d5a9c764..ac27ecf0 100644
--- a/apps/client/src/features/auth/components/back-to-home.tsx
+++ b/apps/client/src/features/auth/components/back-to-home.tsx
@@ -1,8 +1,8 @@
import { Link } from "@tanstack/react-router"
import { ArrowLeft } from "lucide-react"
-import { Button } from "@/libs/ui/button"
-import { MotionContainer } from "@/libs/ui/container"
+import { Button } from "@/libs/ui/components/button"
+import { MotionContainer } from "@/libs/ui/components/container"
export function BackToHome() {
return (
diff --git a/apps/client/src/features/landing/components/cta.tsx b/apps/client/src/features/landing/components/cta.tsx
index 7bf58e5c..3eb42312 100644
--- a/apps/client/src/features/landing/components/cta.tsx
+++ b/apps/client/src/features/landing/components/cta.tsx
@@ -1,8 +1,8 @@
import { Link } from "@tanstack/react-router"
-import { Button } from "@/libs/ui/button"
-import { MotionContainer } from "@/libs/ui/container"
-import { LabelledSeparator } from "@/libs/ui/separator"
+import { Button } from "@/libs/ui/components/button"
+import { MotionContainer } from "@/libs/ui/components/container"
+import { LabelledSeparator } from "@/libs/ui/components/separator"
export function CallToAction() {
return (
diff --git a/apps/client/src/features/landing/components/footer.tsx b/apps/client/src/features/landing/components/footer.tsx
index 455a9d79..6b5adbb9 100644
--- a/apps/client/src/features/landing/components/footer.tsx
+++ b/apps/client/src/features/landing/components/footer.tsx
@@ -1,4 +1,4 @@
-import { MotionContainer } from "@/libs/ui/container"
+import { MotionContainer } from "@/libs/ui/components/container"
export function Footer() {
return (
diff --git a/apps/client/src/features/landing/components/hero.tsx b/apps/client/src/features/landing/components/hero.tsx
index 8335474a..947b3afb 100644
--- a/apps/client/src/features/landing/components/hero.tsx
+++ b/apps/client/src/features/landing/components/hero.tsx
@@ -1,7 +1,7 @@
import { EchoLogo } from "@/common/components/logos/echo-logo"
-import { MotionContainer } from "@/libs/ui/container"
-import H1 from "@/libs/ui/typography/h1"
-import Lead from "@/libs/ui/typography/lead"
+import { MotionContainer } from "@/libs/ui/components/container"
+import H1 from "@/libs/ui/components/typography/h1"
+import Lead from "@/libs/ui/components/typography/lead"
export function Hero() {
return (
diff --git a/apps/client/src/features/onboarding/components/multi-step-loader.tsx b/apps/client/src/features/onboarding/components/multi-step-loader.tsx
index d850dcbf..b9cafa7e 100644
--- a/apps/client/src/features/onboarding/components/multi-step-loader.tsx
+++ b/apps/client/src/features/onboarding/components/multi-step-loader.tsx
@@ -4,8 +4,8 @@ import type { MutationStatus } from "@tanstack/react-query"
import { AnimatePresence } from "motion/react"
import Spinner from "@/common/components/spinner"
-import { Button } from "@/libs/ui/button"
-import { MotionContainer } from "@/libs/ui/container"
+import { Button } from "@/libs/ui/components/button"
+import { MotionContainer } from "@/libs/ui/components/container"
interface Props {
status: MutationStatus
diff --git a/apps/client/src/libs/ui/components/avatar.tsx b/apps/client/src/libs/ui/components/avatar.tsx
new file mode 100644
index 00000000..f00d92bf
--- /dev/null
+++ b/apps/client/src/libs/ui/components/avatar.tsx
@@ -0,0 +1,37 @@
+import * as React from "react"
+
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/libs/ui/utils.ts"
+
+function Avatar({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarImage({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarFallback({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/apps/client/src/libs/ui/button.tsx b/apps/client/src/libs/ui/components/button.tsx
similarity index 98%
rename from apps/client/src/libs/ui/button.tsx
rename to apps/client/src/libs/ui/components/button.tsx
index 1859cf57..82113dd9 100644
--- a/apps/client/src/libs/ui/button.tsx
+++ b/apps/client/src/libs/ui/components/button.tsx
@@ -3,7 +3,7 @@ import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { type VariantProps, cva } from "class-variance-authority"
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
diff --git a/apps/client/src/libs/ui/container.tsx b/apps/client/src/libs/ui/components/container.tsx
similarity index 100%
rename from apps/client/src/libs/ui/container.tsx
rename to apps/client/src/libs/ui/components/container.tsx
diff --git a/apps/client/src/libs/ui/components/dropdown-menu.tsx b/apps/client/src/libs/ui/components/dropdown-menu.tsx
new file mode 100644
index 00000000..10e6c09f
--- /dev/null
+++ b/apps/client/src/libs/ui/components/dropdown-menu.tsx
@@ -0,0 +1,275 @@
+import * as React from "react"
+
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+
+import { cn } from "@/libs/ui/utils.ts"
+
+function DropdownMenu({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuPortal({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuTrigger({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function DropdownMenuGroup({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent
+}
diff --git a/apps/client/src/libs/ui/components/input.tsx b/apps/client/src/libs/ui/components/input.tsx
new file mode 100644
index 00000000..de1acda9
--- /dev/null
+++ b/apps/client/src/libs/ui/components/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react"
+
+import { cn } from "@/libs/ui/utils.ts"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/apps/client/src/libs/ui/page.tsx b/apps/client/src/libs/ui/components/page.tsx
similarity index 94%
rename from apps/client/src/libs/ui/page.tsx
rename to apps/client/src/libs/ui/components/page.tsx
index 63d6c3a8..4d05e9a7 100644
--- a/apps/client/src/libs/ui/page.tsx
+++ b/apps/client/src/libs/ui/components/page.tsx
@@ -1,6 +1,6 @@
import React from "react"
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
type Props = React.ComponentPropsWithoutRef<"div"> & { pad?: boolean; center?: boolean; landingGradient?: boolean }
diff --git a/apps/client/src/libs/ui/components/scroll-area.tsx b/apps/client/src/libs/ui/components/scroll-area.tsx
new file mode 100644
index 00000000..670ad1dd
--- /dev/null
+++ b/apps/client/src/libs/ui/components/scroll-area.tsx
@@ -0,0 +1,52 @@
+import * as React from "react"
+
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/libs/ui/utils.ts"
+
+function ScrollArea({ className, children, ...props }: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function ScrollBar({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { ScrollArea, ScrollBar }
diff --git a/apps/client/src/libs/ui/separator.tsx b/apps/client/src/libs/ui/components/separator.tsx
similarity index 97%
rename from apps/client/src/libs/ui/separator.tsx
rename to apps/client/src/libs/ui/components/separator.tsx
index 79671c2f..515ded6a 100644
--- a/apps/client/src/libs/ui/separator.tsx
+++ b/apps/client/src/libs/ui/components/separator.tsx
@@ -2,7 +2,7 @@ import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
type SeparatorProps = React.ComponentProps
diff --git a/apps/client/src/libs/ui/components/sheet.tsx b/apps/client/src/libs/ui/components/sheet.tsx
new file mode 100644
index 00000000..c43e3b6d
--- /dev/null
+++ b/apps/client/src/libs/ui/components/sheet.tsx
@@ -0,0 +1,145 @@
+import * as React from "react"
+
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { XIcon } from "lucide-react"
+
+import { cn } from "@/libs/ui/utils.ts"
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetTrigger({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetClose({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetPortal({ ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetOverlay({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left"
+}) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ )
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SheetTitle({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SheetDescription({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription }
diff --git a/apps/client/src/libs/ui/components/sidebar.tsx b/apps/client/src/libs/ui/components/sidebar.tsx
new file mode 100644
index 00000000..6a2b2eb7
--- /dev/null
+++ b/apps/client/src/libs/ui/components/sidebar.tsx
@@ -0,0 +1,724 @@
+import React from "react"
+
+import { Slot } from "@radix-ui/react-slot"
+import { type VariantProps, cva } from "class-variance-authority"
+import { PanelLeftIcon } from "lucide-react"
+
+import { Button } from "@/libs/ui/components/button"
+import { Input } from "@/libs/ui/components/input"
+import { Separator } from "@/libs/ui/components/separator"
+import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/libs/ui/components/sheet"
+import { Skeleton } from "@/libs/ui/components/skeleton"
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/libs/ui/components/tooltip"
+import { useIsMobile } from "@/libs/ui/hooks/use-mobile"
+import { cn } from "@/libs/ui/utils"
+
+const SIDEBAR_COOKIE_NAME = "sidebar_state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContextProps = {
+ state: "expanded" | "collapsed"
+ open: boolean
+ setOpen: (open: boolean) => void
+ openMobile: boolean
+ setOpenMobile: (open: boolean) => void
+ isMobile: boolean
+ toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext(undefined)
+
+function useSidebar() {
+ const context = React.useContext(SidebarContext)
+ if (!context) {
+ throw new Error("useSidebar must be used within a SidebarProvider.")
+ }
+
+ return context
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProperty,
+ onOpenChange: setOpenProperty,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ defaultOpen?: boolean
+ open?: boolean
+ onOpenChange?: (open: boolean) => void
+}) {
+ const isMobile = useIsMobile()
+ const [openMobile, setOpenMobile] = React.useState(false)
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen)
+ const open = openProperty ?? _open
+ const setOpen = React.useCallback(
+ async (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value
+ if (setOpenProperty) {
+ setOpenProperty(openState)
+ } else {
+ _setOpen(openState)
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ await cookieStore.set({
+ name: SIDEBAR_COOKIE_NAME,
+ value: `${openState}`,
+ path: "/",
+ expires: Date.now() + SIDEBAR_COOKIE_MAX_AGE
+ })
+ },
+ [setOpenProperty, open]
+ )
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
+ }, [isMobile, setOpen, setOpenMobile])
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
+ event.preventDefault()
+ toggleSidebar()
+ }
+ }
+
+ globalThis.addEventListener("keydown", handleKeyDown)
+ return () => globalThis.removeEventListener("keydown", handleKeyDown)
+ }, [toggleSidebar])
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed"
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ )
+
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
+
+function Sidebar({
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ side?: "left" | "right"
+ variant?: "sidebar" | "floating" | "inset"
+ collapsible?: "offcanvas" | "icon" | "none"
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+ if (collapsible === "none") {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ )
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ )
+}
+
+function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
+ const { toggleSidebar } = useSidebar()
+
+ return (
+
+ )
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
+ return (
+
+ )
+}
+
+function SidebarInput({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarSeparator({ className, ...props }: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "div"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0`,
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0`,
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 md:after:hidden",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarGroupContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+const sidebarMenuButtonVariants = cva(
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left cursor-pointer outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+ outline:
+ "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"
+ },
+ size: {
+ default: "h-10",
+ sm: "h-8 text-sm",
+ lg: "h-12 group-data-[collapsible=icon]:p-0!"
+ }
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default"
+ }
+ }
+)
+
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean
+ isActive?: boolean
+ tooltip?: string | React.ComponentProps
+} & VariantProps) {
+ const Comp = asChild ? Slot : "button"
+ const { isMobile, state } = useSidebar()
+
+ const button = (
+
+ )
+
+ if (!tooltip) {
+ return button
+ }
+
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip
+ }
+ }
+
+ return (
+
+ {button}
+
+
+ )
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean
+ showOnHover?: boolean
+}) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0`,
+ // Increases the hit area of the button on mobile.
+ "after:absolute after:-inset-2 md:after:hidden",
+ "peer-data-[size=sm]/menu-button:top-1",
+ "peer-data-[size=default]/menu-button:top-1.5",
+ "peer-data-[size=lg]/menu-button:top-2.5",
+ "group-data-[collapsible=icon]:hidden",
+ showOnHover &&
+ `peer-data-[active=true]/menu-button:text-sidebar-accent-foreground
+ group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100
+ data-[state=open]:opacity-100 md:opacity-0`,
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showIcon?: boolean
+}) {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`
+ }, [])
+
+ return (
+
+ {showIcon && (
+
+ )}
+
+
+ )
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ )
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = "md",
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean
+ size?: "sm" | "md"
+ isActive?: boolean
+}) {
+ const Comp = asChild ? Slot : "a"
+
+ return (
+ svg]:text-sidebar-accent-foreground
+ flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden
+ focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none
+ aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0`,
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+ size === "sm" && "text-xs",
+ size === "md" && "text-sm",
+ "group-data-[collapsible=icon]:hidden",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger,
+ // eslint-disable-next-line react-refresh/only-export-components
+ useSidebar
+}
diff --git a/apps/client/src/libs/ui/components/skeleton.tsx b/apps/client/src/libs/ui/components/skeleton.tsx
new file mode 100644
index 00000000..77c959ca
--- /dev/null
+++ b/apps/client/src/libs/ui/components/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/libs/ui/utils.ts"
+
+function Skeleton({ className, ...props }: Readonly>) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/apps/client/src/libs/ui/components/tooltip.tsx b/apps/client/src/libs/ui/components/tooltip.tsx
new file mode 100644
index 00000000..3ce84743
--- /dev/null
+++ b/apps/client/src/libs/ui/components/tooltip.tsx
@@ -0,0 +1,71 @@
+import React from "react"
+
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/libs/ui/utils.ts"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: Readonly>) {
+ return (
+
+ )
+}
+
+function Tooltip({ ...props }: Readonly>) {
+ return (
+
+
+
+ )
+}
+
+function TooltipTrigger({ ...props }: Readonly>) {
+ return (
+
+ )
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: Readonly>) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/apps/client/src/libs/ui/typography/h1.tsx b/apps/client/src/libs/ui/components/typography/h1.tsx
similarity index 89%
rename from apps/client/src/libs/ui/typography/h1.tsx
rename to apps/client/src/libs/ui/components/typography/h1.tsx
index 4299aebe..aa6fa6f0 100644
--- a/apps/client/src/libs/ui/typography/h1.tsx
+++ b/apps/client/src/libs/ui/components/typography/h1.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/typography/h3.tsx b/apps/client/src/libs/ui/components/typography/h3.tsx
similarity index 87%
rename from apps/client/src/libs/ui/typography/h3.tsx
rename to apps/client/src/libs/ui/components/typography/h3.tsx
index ce28fb62..97e31f86 100644
--- a/apps/client/src/libs/ui/typography/h3.tsx
+++ b/apps/client/src/libs/ui/components/typography/h3.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/typography/large.tsx b/apps/client/src/libs/ui/components/typography/large.tsx
similarity index 86%
rename from apps/client/src/libs/ui/typography/large.tsx
rename to apps/client/src/libs/ui/components/typography/large.tsx
index b53dad97..da681111 100644
--- a/apps/client/src/libs/ui/typography/large.tsx
+++ b/apps/client/src/libs/ui/components/typography/large.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/typography/lead.tsx b/apps/client/src/libs/ui/components/typography/lead.tsx
similarity index 86%
rename from apps/client/src/libs/ui/typography/lead.tsx
rename to apps/client/src/libs/ui/components/typography/lead.tsx
index fd977a5e..b3e00131 100644
--- a/apps/client/src/libs/ui/typography/lead.tsx
+++ b/apps/client/src/libs/ui/components/typography/lead.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/typography/muted.tsx b/apps/client/src/libs/ui/components/typography/muted.tsx
similarity index 86%
rename from apps/client/src/libs/ui/typography/muted.tsx
rename to apps/client/src/libs/ui/components/typography/muted.tsx
index 74ef0892..ff985ee6 100644
--- a/apps/client/src/libs/ui/typography/muted.tsx
+++ b/apps/client/src/libs/ui/components/typography/muted.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/typography/p.tsx b/apps/client/src/libs/ui/components/typography/p.tsx
similarity index 86%
rename from apps/client/src/libs/ui/typography/p.tsx
rename to apps/client/src/libs/ui/components/typography/p.tsx
index c5e402f1..12fed24a 100644
--- a/apps/client/src/libs/ui/typography/p.tsx
+++ b/apps/client/src/libs/ui/components/typography/p.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/typography/small.tsx b/apps/client/src/libs/ui/components/typography/small.tsx
similarity index 87%
rename from apps/client/src/libs/ui/typography/small.tsx
rename to apps/client/src/libs/ui/components/typography/small.tsx
index 85aa8ebe..0cafd521 100644
--- a/apps/client/src/libs/ui/typography/small.tsx
+++ b/apps/client/src/libs/ui/components/typography/small.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/libs/utils"
+import { cn } from "@/libs/ui/utils"
interface Props {
className?: string
diff --git a/apps/client/src/libs/ui/hooks/use-mobile.ts b/apps/client/src/libs/ui/hooks/use-mobile.ts
new file mode 100644
index 00000000..8f06f2d0
--- /dev/null
+++ b/apps/client/src/libs/ui/hooks/use-mobile.ts
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+const MAX_WIDTH_MQ = globalThis.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+
+const onMaxWidthChange = (callback: () => void) => {
+ const mq = MAX_WIDTH_MQ
+
+ const handleChange = () => callback()
+
+ mq.addEventListener("change", handleChange)
+ return () => mq.removeEventListener("change", handleChange)
+}
+
+function useIsMobile() {
+ const [isMobile, setIsMobile] = useState()
+
+ useEffect(() => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+
+ const unsubscribe = onMaxWidthChange(() => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT))
+ return unsubscribe
+ }, [])
+
+ return !!isMobile
+}
+
+export { useIsMobile }
diff --git a/apps/client/src/libs/utils/index.ts b/apps/client/src/libs/ui/utils.ts
similarity index 100%
rename from apps/client/src/libs/utils/index.ts
rename to apps/client/src/libs/ui/utils.ts
diff --git a/apps/client/src/main.css b/apps/client/src/main.css
index 99e2c0f1..7401a54d 100644
--- a/apps/client/src/main.css
+++ b/apps/client/src/main.css
@@ -2,6 +2,8 @@
@import "tailwindcss";
@import "tw-animate-css";
+@custom-variant dark (&:is(.dark *));
+
@layer base {
* {
@apply border-border outline-ring/50;
@@ -49,17 +51,14 @@
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
-
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
-
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
-
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
@@ -67,6 +66,14 @@
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
}
:root {
@@ -126,19 +133,15 @@
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
- /* --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: oklch(0.205 0 0);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0); */
+ /* sidebar */
+ --sidebar: hsl(0 0% 98%);
+ --sidebar-foreground: hsl(240 5.3% 26.1%);
+ --sidebar-primary: hsl(240 5.9% 10%);
+ --sidebar-primary-foreground: hsl(0 0% 98%);
+ --sidebar-accent: hsl(240 4.8% 95.9%);
+ --sidebar-accent-foreground: hsl(240 5.9% 10%);
+ --sidebar-border: hsl(220 13% 91%);
+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
}
:root[data-theme="dark"] {
@@ -168,11 +171,7 @@
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
- /* --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
+ /* sidebar */
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
@@ -180,7 +179,7 @@
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.556 0 0); */
+ --sidebar-ring: oklch(0.556 0 0);
}
html,
diff --git a/apps/client/src/routes/(app)/_layout.tsx b/apps/client/src/routes/(app)/_layout.tsx
index 86bc9e96..7fad78d8 100644
--- a/apps/client/src/routes/(app)/_layout.tsx
+++ b/apps/client/src/routes/(app)/_layout.tsx
@@ -1,6 +1,9 @@
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"
+import { AppSidebar } from "@/common/components/sidebar/sidebar"
import { isAuthenticated, isOnboarded } from "@/common/utils/auth"
+import { ScrollArea } from "@/libs/ui/components/scroll-area"
+import { SidebarProvider } from "@/libs/ui/components/sidebar"
/**
* Routes nested within the `/(app)` pathless layout should only be accessible
@@ -25,5 +28,16 @@ export const Route = createFileRoute("/(app)")({
})
}
},
- component: () =>
+ component: AppLayout
})
+
+function AppLayout() {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/client/src/routes/(app)/home.tsx b/apps/client/src/routes/(app)/home.tsx
index 131c3c91..2767d224 100644
--- a/apps/client/src/routes/(app)/home.tsx
+++ b/apps/client/src/routes/(app)/home.tsx
@@ -20,9 +20,11 @@ function HomePage() {
}, [])
return (
-
- Hello "/(protected)/home"!
-
-
+ <>
+
+
+ Hello "/(app)/home"!
+
+ >
)
}
diff --git a/apps/client/src/routes/(auth)/_layout.tsx b/apps/client/src/routes/(auth)/_layout.tsx
index 73438d48..92915df3 100644
--- a/apps/client/src/routes/(auth)/_layout.tsx
+++ b/apps/client/src/routes/(auth)/_layout.tsx
@@ -1,7 +1,7 @@
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"
import { isAuthenticated } from "@/common/utils/auth"
-import { Page } from "@/libs/ui/page"
+import { Page } from "@/libs/ui/components/page"
/**
* Routes nested within the `/(auth)` pathless layout should only be accessible
diff --git a/apps/client/src/routes/(auth)/index.tsx b/apps/client/src/routes/(auth)/index.tsx
index f731d6a0..c8a7b61e 100644
--- a/apps/client/src/routes/(auth)/index.tsx
+++ b/apps/client/src/routes/(auth)/index.tsx
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router"
import { CallToAction } from "@/features/landing/components/cta"
import { Footer } from "@/features/landing/components/footer"
import { Hero } from "@/features/landing/components/hero"
-import { Container } from "@/libs/ui/container"
+import { Container } from "@/libs/ui/components/container"
export const Route = createFileRoute("/(auth)/")({
component: LandingPage
diff --git a/apps/client/src/routes/(onboarding)/onboarding.tsx b/apps/client/src/routes/(onboarding)/onboarding.tsx
index a4015377..f2c55078 100644
--- a/apps/client/src/routes/(onboarding)/onboarding.tsx
+++ b/apps/client/src/routes/(onboarding)/onboarding.tsx
@@ -4,7 +4,7 @@ import { createFileRoute } from "@tanstack/react-router"
import { useEffectOnce } from "@/common/hooks/use-effect-once"
import { onboardingMutationOptions } from "@/features/onboarding/api/options"
import { OnboardingAnimation } from "@/features/onboarding/components/multi-step-loader"
-import { Page } from "@/libs/ui/page"
+import { Page } from "@/libs/ui/components/page"
export const Route = createFileRoute("/(onboarding)/onboarding")({
component: OnboardingPage
diff --git a/apps/client/src/routes/__root.tsx b/apps/client/src/routes/__root.tsx
index 5a6d8c52..e253e5b3 100644
--- a/apps/client/src/routes/__root.tsx
+++ b/apps/client/src/routes/__root.tsx
@@ -12,7 +12,7 @@ export const Route = createRootRouteWithContext()({
component: () => (
<>
-
+
>
)
})